Skip to content

Latest commit

 

History

History
1113 lines (764 loc) · 42.4 KB

lang_readme.md

File metadata and controls

1113 lines (764 loc) · 42.4 KB

Overview

lang.mjs provides tools essential for all other code. Stuff that should be built into the language.

  • Type checks and assertions.
    • Terse.
    • Performant.
    • Minifiable.
    • Descriptive.
  • Sensible type conversions.

Port and rework of https://github.com/mitranim/fpx.

TOC

Usage

import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/lang.mjs'

API

function isNil

Links: source; test/example.

True for null and undefined. Same as value == null. Incidentally, these are the only values that produce an exception when attempting to read a property: null.someProperty.

import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/lang.mjs'

// Definition
function isNil(value) {return value == null}

l.isNil(null)
// true

l.isNil(undefined)
// true

l.isNil(false)
// false

function isSome

Links: source; test/example.

Inverse of #isNil. False for null and undefined, true for other values.

function isBool

Links: source; test/example.

Same as typeof val === 'boolean'.

function isNum

Links: source; test/example.

Same as typeof val === 'number'. True if the value is a primitive number, including NaN and ±Infinity. In most cases you should use #isFin instead.

import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/lang.mjs'

l.isNum(1)
// true

l.isNum('1')
// false

l.isNum(NaN)
// true <-- WTF

function isFin

Links: source; test/example.

Same as ES2015's Number.isFinite. True if val is a primitive number and is not NaN or ±Infinity. In most cases you should prefer isFin over isNum.

import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/lang.mjs'

l.isFin(1)
// true

l.isFin('1')
// false

l.isFin(NaN)
// false

function isFinNeg

Links: source; test/example.

True if the value is finite (via #isFin) and < 0.

function isFinPos

Links: source; test/example.

True if the value is finite (via #isFin) and > 0.

function isInt

Links: source; test/example.

True if the value is an integer: finite via #isFin, without a fractional part.

function isNat

Links: source; test/example.

True if the value is a natural number: integer >= 0. Also see #isIntPos.

function isIntNeg

Links: source; test/example.

True if the value is integer < 0. Also see #isFinNeg.

function isIntPos

Links: source; test/example.

True if the value is integer > 0. Also see #isNat, #isFinPos.

function isNaN

Links: source; test/example.

Same as ES2015's Number.isNaN. True if the value is actually NaN. Doesn't coerce non-numbers to numbers, unlike global isNaN.

function isInf

Links: source; test/example.

True if the value is -Infinity or Infinity.

function isBigInt

Links: source; test/example.

True if the value is a primitive BigInt. False for all other inputs, including BigInt object wrappers.

function isStr

Links: source; test/example.

Same as typeof val === 'string'. True if the value is a primitive string.

function isSym

Links: source; test/example.

Same as typeof val === 'symbol'. True if the value is a primitive symbol.

function isKey

Links: source; test/example.

True if the value is primitive and usable as a map key. True for all primitives excluding garbage values via #isJunk.

function isStructKey

Links: source; test/example.

True if the value qualifies as an object property key: either a string or a symbol.

function isPk

Links: source; test/example.

True for objects that implement method .pk which must return a valid #primary. This interface is used internally by Coll.

function isJunk

Links: source; test/example.

True for garbage values: #nil, #NaN, #±Infinity.

function isComp

Links: source; test/example.

True if the value is "composite" / "compound" / "complex". Opposite of #isPrim. Definition:

function isComp(val) {return isObj(val) || isFun(val)}

function isPrim

Links: source; test/example.

True if the value is a JS primitive: not an object, not a function. Opposite of #isComp.

function isFun

Links: source; test/example.

Same as typeof val === 'function'. True if the value is any function, regardless of its type (arrow, async, generator, etc.).

function isFunSync

Links: source; test/example.

True if the input is a normal sync function. False for generator functions or async functions.

function isFunGen

Links: source; test/example.

True if the input is a sync generator function. False for normal sync functions and async functions.

function isFunAsync

Links: source; test/example.

True if the input is an async non-generator function. False for sync functions, generator functions, or async generator functions.

function isFunAsyncGen

Links: source; test/example.

True if the input is an async generator function. False for sync functions and async non-generator functions.

function isObj

Links: source; test/example.

Same as typeof val === 'object' && val !== null. True for any JS object: plain dict, array, various other classes. Doesn't include functions, even though JS functions are extensible objects.

  • Compare #isComp which returns true for objects and functions.
  • For plain objects used as dictionaries, see #isDict.
  • For fancy non-list objects, see #isStruct.

function isDict

Links: source; test/example.

True for a "plain object" created via {...} or Object.create(null). False for any other input, including instances of any class other than Object.

See #isStruct for a more general definition of a non-iterable object.

function isStruct

Links: source; test/example.

True if the value is a non-iterable object. Excludes both #sync_iterables and #async_iterables. Note that #dicts are automatically structs, but not all structs are dicts.

function isArr

Links: source; test/example.

Alias for Array.isArray. Used internally for all array checks.

True if the value is an instance of Array or its subclass. False for all other values, including non-array objects whose prototype is an array.

function isTrueArr

Links: source; test/example.

Similar to Array.isArray and #isArr, but returns true only for instances of the exact Array class, false for instances of subclasses.

At the time of writing, subclasses of Array suffer horrible performance penalties in V8, and possibly in other engines. Using them can also cause deoptimization of code that would otherwise run much faster. We sometimes prioritize or even enforce "true" arrays for consistent performance.

function isReg

Links: source; test/example.

True if the value is an instance of RegExp or its subclass.

function isDate

Links: source; test/example.

True of value is an instance of Date. Most of the time you should prefer #isValidDate.

function isValidDate

Links: source; test/example.

True of value is an instance of Date and its timestamp is #finite rather than NaN or Infinity.

function isInvalidDate

Links: source; test/example.

True of value is an instance of Date representing an invalid date whose timestamp is NaN.

function isSet

Links: source; test/example.

True if the value is an instance of Set or its subclass.

function isMap

Links: source; test/example.

True if the value is an instance of Map or its subclass.

function isPromise

Links: source; test/example.

True if the value satisfies the ES2015 promise interface.

function isIter

Links: source; test/example.

True if the value satisfies the ES2015 sync iterable interface. For iterator rather than iterable, use #isIterator.

function isIterAsync

Links: source; test/example.

True if the value satisfies the ES2015 async iterable interface. For iterator rather than iterable, use #isIteratorAsync.

function isIterator

Links: source; test/example.

True if the value satisfies the ES2015 sync iterator interface. For iterable rather than iterator, use #isIter.

function isIteratorAsync

Links: source; test/example.

True if the value satisfies the ES2015 async iterator interface. For iterable rather than iterator, use #isIterAsync.

function isGen

Links: source; test/example.

True if the value is a #sync_iterator created by calling a generator function.

function isCls

Links: source; test/example.

True if the input is a function with a prototype, likely to be a class. False for arrow functions such as () => {}, which don't have a prototype.

function isList

Links: source; test/example.

True for any array-like such as: [], arguments, TypedArray, NodeList, etc. Used internally for most list checks. Note that primitive strings are not considered lists.

function isSeq

Links: source; test/example.

True for any of:

Many functions in iter.mjs support arbitrary data structures compatible with values, but some functions such as arr allow only sequences, for sanity checking.

function isVac

Links: source; test/example.

Short for "is vacuous" or "is vacated". Could also be called "is falsy deep". True if the input is #falsy or a #list where all values are vacuous, recursively. Does not iterate non-lists. Also see complementary function #vac.

function isScalar

Links: source; test/example.

True for a value that could be considered a single scalar, rather than a collection / data structure. Currently this is equivalent to the concept of an intentionally stringable value. In the future, we may consider renaming this function or splitting the concepts.

The following are included:

  • Any #primitive except for those which are excluded below.
  • Any #object with a special .toString method, distinct from both Object.prototype.toString and Array.prototype.toString. Examples include #dates, URL, and many more.

The following are excluded:

  • Any #nil.
  • Any #symbol.
  • Any object without a special .toString method.

To include nil, use #isScalarOpt.

function isEmpty

Links: source; test/example.

True if the input is an empty collection such as list, set, map, or a primitive such as null. False for any other non-primitive. Treating primitives as "empty" is consistent with various functions in iter.mjs that operate on collections.

function isInst

Links: source; test/example.

Signature: (val, Cls) => bool.

Same as instanceof but does not implicitly convert the operand to an object. True only if the operand is already an instance of the given class. Also unlike instanceof, this is always false for functions, avoiding the insanity of fun instanceof Function being true.

function req

Links: source; test/example.

Signature: (val, test) => val where test: val => bool.

Short for "require". Minification-friendly assertion. If !test(val), throws an informative TypeError. Otherwise, returns val as-is.

import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/lang.mjs'

l.req({one: `two`}, l.isObj)
// {one: `two`}

l.req('str', l.isFun)
// Uncaught TypeError: expected variant of isFun, got "str"

function opt

Links: source; test/example.

Short for "optional". If val is #non_nil, uses #req to validate it. Returns val as-is.

function reqInst

Links: source; test/example.

Signature: (val, Cls) => val.

Short for "require instance". Asserts that val is an instance of the given class. Returns val as-is.

function optInst

Links: source; test/example.

Short for "optional instance". If val is #non_nil, uses #reqInst to validate it. Returns val as-is.

function only

Links: source; test/example.

Signature: (val, test) => val where test: val => bool.

Type filtering utility. If val satisfies the given test function, returns val as-is. Otherwise returns undefined.

function onlyInst

Links: source; test/example.

Signature: (val, Cls) => val?.

Type filtering utility. If val is an instance of Cls, returns val as-is. Otherwise returns undefined.

function render

Links: source; test/example.

Renders a value for user display. Counterpart to #show, which renders a value for debug purposes. Intended only for #scalar values. Rules:

  • #Date with default .toString → use .toISOString. This overrides the insane JS default stringification of dates, defaulting to the reversible machine-decodable representation used for JSON.
  • Other #non-nil #scalars → default JS stringification.
  • All other inputs including #nilTypeError exception.

function renderLax

Links: source; test/example.

Renders a value for user display. Intended only for #scalar values. Unlike #render, this allows nil. Rules:

function show

Links: source; test/example.

Renders a value for debug purposes. Counterpart to #render, which renders a value for user display. Convenient for interpolating things into error messages. Used internally in assertion functions such as #req. Approximate rules:

  • String → use JSON.stringify.
  • Function → [function ${val.name || val}].
    • For named functions, this shorter representation is usually preferable to printing the entire source code.
  • Object →
    • Plain {} or [] → use JSON.stringify.
    • Otherwise [object <name>], prioritizing constructor name over Symbol.toStringTag.
      • Exact opposite of default behavior for Object.prototype.toString.
  • Otherwise → default JS stringification.

function toTrueArr

Links: source; test/example.

Idempotent conversion to a #true. Allowed inputs:

  • #Nil → return [].
  • #True → return as-is.
  • #Iterable → convert to Array.
  • Otherwise → TypeError exception.

function is

Links: source; test/example.

Identity test: same as ===, but considers NaN equal to NaN. Equivalent to SameValueZero as defined by the language spec. Used internally for all identity tests.

Note that Object.is implements SameValue, which treats -0 and +0 as distinct values. This is typically undesirable. As a result, you should prefer l.is over === or Object.is.

import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/lang.mjs'

l.is(1, '1')
// false

l.is(NaN, NaN)
// true

function truthy

Links: source; test/example.

Same as !! or Boolean. Sometimes useful with higher-order functions.

function falsy

Links: source; test/example.

Same as !. Sometimes useful with higher-order functions.

function nop

Links: source; test/example.

Empty function. Functional equivalent of ; or undefined. Sometimes useful with higher-order functions.

function id

Links: source; test/example.

Identity function: returns its first argument unchanged. Sometimes useful with higher-order functions.

function val

Links: source; test/example.

Takes a value and creates a function that always returns that value. Sometimes useful with higher order functions.

import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/lang.mjs'

const constant = l.val(1)

constant()
// 1

constant(`this input is ignored`)
// 1

function panic

Links: source; test/example.

Same as throw but an expression rather than a statement. Also sometimes useful with higher-order functions.

import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/lang.mjs'

const x = someTest ? someValue : l.panic(Error(`unreachable`))

function vac

Links: source; test/example.

Complements #isVac. Returns undefined if the input is vacuous, otherwise returns the input as-is.

function bind

Links: source; test/example.

Like Function.prototype.bind, but instead of taking this as an argument, takes it contextually. By default this is undefined. To set it, use l.bind.call.

Returns a new function that represents partial application of the given function, a common tool in functional programming. When called, it joins arguments from both calls and invokes the original function. Think of it like splitting a function call in two, or more. Performance is inferior to closures; avoid in hotspots.

import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/lang.mjs'

const inc = l.bind(l.add, 1)

inc(2)
// 3

Note: we don't provide facilities for currying. Experience has shown it to be extremely error prone. Currying, as seen in purely functional languages such as Haskell, tends to care about the amount of arguments. Calling a curried function may either create a new function, or call the underlying function (possibly side-effectful). This approach works reasonably well in statically typed languages, but not in JS where all functions are variadic and it's conventional to sometimes pass extra utility arguments "just in case", which the callee may or may not care about. bind is different because the created function will always call the original function, regardless of how many arguments were passed.

function not

Links: source; test/example.

Returns a new function that negates the result of the given function, like a delayed !.

import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/lang.mjs'

function eq(a, b) {return a === b}

const different = l.not(eq)

different(10, 20)
// !eq(10, 20) = true

// equivalent:
function different(a, b) {return !eq(a, b)}

function hasOwn

Links: source; test/example.

Same as Object.prototype.hasOwnProperty but shorter and safe to call on primitives. Always false for primitives.

function hasOwnEnum

Links: source; test/example.

Same as Object.prototype.propertyIsEnumerable but shorter and safe to call on primitives. Always false for primitives.

function hasInherited

Links: source; test/example.

Returns true if the target is #non-primitive and has the given property on its prototype. As a consequence, this returns false if the target is a primitive, or has the given property as an "own" property, either enumerable or not.

import * as l from 'https://cdn.jsdelivr.net/npm/@mitranim/[email protected]/lang.mjs'

l.hasInherited([10, 20, 30], `length`)
// false

l.hasInherited([10, 20, 30], `1`)
// false

l.hasInherited([10, 20, 30], `toString`)
// true

function hasMeth

Links: source; test/example.

True if the the given value has the given named method. Safe to call on primitives such as null. Always false for primitives.

function setProto

Links: source; test/example.

Workaround for bugs related to subclassing.

In some Safari versions, when instantiating a subclass of various recent built-in classes such as Request/Response/URL, the engine incorrectly uses the prototype of the superclass rather than the subclass. Occurs in Safari 12-14+, both desktop and mobile. This seems to fix that. Example:

class Abort extends AbortController {
  constructor() {
    super()
    l.setProto(this, new.target)
  }
}

The following version is shorter but more confusing if you don't know full semantics of JS classes:

class Abort extends AbortController {
  constructor() {l.setProto(super(), new.target)}
}

function Emp

Links: source; test/example.

Short for "empty". Hybrid function / superclass for empty objects.

In function mode, Emp() returns Object.create(null), with no measurable overhead. Basically a syntactic shortcut.

Calling new Emp() also returns Object.create(null). This is pointless and should be avoided.

Subclassing Emp creates a class with the cleanest possible .prototype, which is null-based, sharing no common ancestry with anything.

class Empty extends l.Emp {}
Object.getPrototypeOf(Empty.prototype) === null

// Instantiation and inheritance works as expected.
const val = new Empty()
val instanceof Empty
val.constructor === Empty

// `Object` stuff is not inherited.
!(val instanceof Object)
!(`toString` in val)

function add

Links: source; test/example.

Same as +.

function sub

Links: source; test/example.

Same as -.

function mul

Links: source; test/example.

Same as *.

function div

Links: source; test/example.

Same as /.

function rem

Links: source; test/example.

Same as %.

function lt

Links: source; test/example.

Same as <.

function gt

Links: source; test/example.

Same as >.

function lte

Links: source; test/example.

Same as <=.

function gte

Links: source; test/example.

Same as >=.

function neg

Links: source; test/example.

Arithmetic negation. Same as unary -.

function inc

Links: source; test/example.

Increments by 1.

function dec

Links: source; test/example.

Decrements by 1.

Undocumented

The following APIs are exported but undocumented. Check lang.mjs.