Skip to content

joakim/ternary

Repository files navigation

Ternary

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 | undefined

With common logical connectives:

As well as:

And conditional operations:

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).

What for?

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.

Installation

With your package manager of choice, install @joakim/ternary from JSR.

Usage

See the documentation.

Semantics

When working with ternary logic, one must choose the semantics of undefined, which will vary by use case.

That means deciding:

  1. What the third value means

    • Kleene's K₃: not known/knowable
    • Łukasiewicz's Ł₃: not yet known
    • Priest's LP: known to be simultaneously true and false
  2. How to handle the truth

    • resolve it, acknowledging the existence of the third value
    • collapse it, with undefined evaluated as true (LP style)
    • Let JavaScript handle it, coercing undefined to false in conditionals

Example

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
}

Tips

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.

Truth tables

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 = undefined or unknown
  • T = true

not

Flips true and false, undefined remains the same.

A NOT A
F T
U U
T F

both / and

The result is the least true of the operands.

  • If any operand is false, it returns false
  • Otherwise, if any operand is undefined, it returns undefined
  • Otherwise, both operands are true, so it returns true

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

either / or

The result is the most true of the operands.

  • If any operand is true, it returns true
  • Otherwise, if any operand is undefined, it returns undefined
  • Otherwise, both operands are false, so it returns false

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

differ / xor

The result is true only when the operands are different boolean values.

  • If one operand is true and the other is false, it returns true
  • If both operands are true or both are false, it returns false
  • If any operand is undefined, it returns undefined

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

same / xnor

The result is true only when the operands are the same boolean value.

  • If both operands are true or both are false, it returns true
  • If one operand is true and the other is false, it returns false
  • If any operand is undefined, it returns undefined

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

License

This work is dual-licensed under both MIT and XPL. You may choose either of the two.

SPDX-License-Identifier: MIT OR XPL

About

Three-valued logic with JavaScript primitives

Resources

License

Unknown and 2 other licenses found

Licenses found

Unknown
LICENSE
MIT
LICENSE.MIT
Unknown
LICENSE.XPL

Stars

Watchers

Forks