A lightweight, type-safe implementation of three-valued logic (3VL) that enables nuanced logical operations where truth can be indeterminate (Łukasiewicz's Ł₃), unknown (Kleene's K₃) or paradoxical (Priest's LP).
Nothing fancy, just:
type ternary = boolean | undefinedWith common logical connectives:
As well as:
And conditional operations:
resolve, a conditional that is actually ternary (Ł₃/K₃)collapse, a paraconsistent conditional (LP)
These functions enforce strict equality, unlike JavaScript's sloppy definition of truth.
Also included is Ternary, a function/class like Boolean, for coercing any value to a ternary value (sloppy, because JavaScript), as well as toBoolean for collapsing a ternary value to a boolean using Priest's Logic of Paradox.
There's also an alternative implementation with balanced ternary (-1, 0, +1).
Sometimes things aren't quite so binary.
true |
false |
undefined |
Logic | |
|---|---|---|---|---|
| Unknowability | true | false | unknown | K₃ |
| Undecidability | true | false | undecidable | K₃ |
| Indeterminacy | true | false | indeterminate | Ł₃ |
| Paradoxical | true | false | true and false | LP |
For example:
true |
false |
undefined |
|
|---|---|---|---|
| Optional input | yes | no | blank |
| Ternary input | yes | no | not applicable |
| Access control | allow | deny | not specified |
| Decision system | approve | reject | needs review |
| Electrical charge | positive | negative | no charge |
Ternary – either you need it, you don't need it, or you don't know whether you need it.
A variable that is true | false | undefined is already ternary. This library merely formalizes that and provides useful functions for correct evaluation using ternary logic.
In Kleene/Łukasiewicz's ternary logic, undefined propagates: a compound condition is true or false only if it is definitely true or definitely false; otherwise it is undefined.
In Priest's Logic of Paradox, undefined also propagates, but it signifies simultaneously true and false. This enables collapsing a ternary condition to a boolean, where it becomes true if it is definitely true or simultaneously true and false (undefined).
In JavaScript's own conditionals, undefined is coerced to false.
With your package manager of choice, install @joakim/ternary from JSR.
See the documentation.
When working with ternary logic, one must choose the semantics of undefined, which will vary by use case.
That means deciding:
-
What the third value means
- Kleene's K₃: not known/knowable
- Łukasiewicz's Ł₃: not yet known
- Priest's LP: known to be simultaneously
trueandfalse
-
How to handle the truth
To illustrate, here's a naïve example of using ternary logic to determine loan eligibility:
import { type ternary, both, either, resolve } from '@joakim/ternary'
type LoanApplicantStatus = {
income: ternary
debt: ternary
assets: ternary
history: ternary
}
function check({ income, debt, assets, history }: LoanApplicantStatus) {
let result = either(both(income, debt), both(assets, history))
return resolve(result, "Approve", "Reject", "Needs review")
}
check({ income: true, debt: false, assets: true, history: true }) // "Approve"
check({ income: true, debt: undefined, assets: false, history: false }) // "Needs review"
check({ income: false, debt: undefined, assets: true, history: false }) // "Reject"When working with ternary logic, it often makes most sense to acknowledge the third value with the resolve conditional.
If the example had used collapse, undefined would have been evaluated as true, effectively approving any loan application that would otherwise need review. Very generous, but not what you'd want.
collapse(result, "Approve", "Reject")If the example had used JavaScript's own conditionals, undefined would have been coerced to false, effectively rejecting any application that would otherwise need review. Not what you'd want either.
If, however, it makes sense to interpret the value using Priest's Logic of Paradox, it can be safely collapsed to a boolean for use in JavaScript's conditionals.
if (toBoolean(result)) {
// true, possibly paradoxical
}In general, the functions lend themselves to a functional programming style.
Arguments of resolve and collapse can be functions, allowing one to call the returned function directly. They are also generic, allowing one to specify a return type.
resolve<ApplicationHandler>(result, approve, reject, review)(application)Only the condition (first argument) and the true case (second argument) are required. The remaining arguments are optional, defaulting to undefined.
The following logical connectives come in two variants, one for n-ary arguments and one for 2 arguments.
| Connective | n-ary | 2 arguments |
|---|---|---|
| AND | and |
both |
| OR | or |
either |
| XNOR | xnor |
same |
| XOR | xor |
differ |
The 2 argument versions have more intuitive names, making logical expressions read almost like sentences, and should perform better.
A quick guide to ternary logic
While obviously more complex than boolean logic, ternary is not as difficult as it looks once you get the hang of it. It helps to think of undefined as an unknown truth value that could potentially be true or false, we just don't know.
- F =
false - U =
undefinedor unknown - T =
true
Flips true and false, undefined remains the same.
| A | NOT A |
|---|---|
| F | T |
| U | U |
| T | F |
The result is the least true of the operands.
- If any operand is
false, it returnsfalse - Otherwise, if any operand is
undefined, it returnsundefined - Otherwise, both operands are
true, so it returnstrue
It's just like boolean and, except undefined wins out over true.
| F | U | T | |
|---|---|---|---|
| F | F | F | F |
| U | F | U | U |
| T | F | U | T |
The result is the most true of the operands.
- If any operand is
true, it returnstrue - Otherwise, if any operand is
undefined, it returnsundefined - Otherwise, both operands are
false, so it returnsfalse
It's just like boolean or, except undefined wins out over false.
| F | U | T | |
|---|---|---|---|
| F | F | U | T |
| U | U | U | T |
| T | T | T | T |
The result is true only when the operands are different boolean values.
- If one operand is
trueand the other isfalse, it returnstrue - If both operands are
trueor both arefalse, it returnsfalse - If any operand is
undefined, it returnsundefined
It's just like boolean xor, except undefined always wins out.
| F | U | T | |
|---|---|---|---|
| F | F | U | T |
| U | U | U | U |
| T | T | U | F |
The result is true only when the operands are the same boolean value.
- If both operands are
trueor both arefalse, it returnstrue - If one operand is
trueand the other isfalse, it returnsfalse - If any operand is
undefined, it returnsundefined
It's just like boolean xnor, except undefined always wins out.
| F | U | T | |
|---|---|---|---|
| F | T | U | F |
| U | U | U | U |
| T | F | U | T |
This work is dual-licensed under both MIT and XPL. You may choose either of the two.
SPDX-License-Identifier: MIT OR XPL