green_curry

Curried functional programming library

Usage no npm install needed!

<script type="module">
  import greenCurry from 'https://cdn.skypack.dev/green_curry';
</script>

README

green_curry: Curried functional programming library

Colorful and delicious!

(ES6 required)

(please disregard the culture references in the examples)

The contents of this package are organized into submodules:

F: general functions and constants

A: arrays

D: dictionaries

S: strings

This library is to provide support for pure functional programming by providing convenience functions in curried form.

Here is an example:

// summing up an array
function sum_array(array) {
  var ans = 0;
  for (var i = 0; i < array.length; i++) {
    ans += array[i];
  }
  return ans;
}

// summing up a array of arrays
function sum_arrays(array) {
  var ans = 0;
  for (var i = 0; i < array.length; i++) {
    ans += sum_array(array[i]);
  }
  return ans;
}

// summing up a array of arrays of arrays
function sum_arrays2(array) {
  var ans = 0;
  for (var i = 0; i < array.length; i++) {
    ans += sum_arrays(array[i]);
  }
  return ans;
}

The above code, functionally:

const green_curry = require ('green_curry') (['globalize', 'short F.c']})

// summing up a array
const sum_array = A.fold (F ['+']) (0)

// summing up a array of arrays
const sum_arrays = F.c (A.map (sum_array) >> sum_array)

// summing up a array of arrays of arrays
const sum_arrays2 = F.c (A.map (sum_arrays) >> sum_array)

An understanding of the typed lambda calculus, currying, JavaScript type system, closures, and mutability are recommended for effective use of this library. All functions are free of self-references, allowing their safe use as first-class functions. All functions are pure, except globalize, F.c, and F.p.

Initializer options

Initializer options are compatible with each other

globalize

Pulls the included submodules into global scope to obviate the need for fully-qualifying each resource

All examples on this page will assume this has already been called

(note: works both in-server and in-browser)

var green_curry = require ('green_curry') (['globalize'])
F.log ('Hint: 3?') // prints 'Hint: 3?'

short F.c

Removes the need for the first call to F.c

(note: this will break compatibility of nested F.c and F.p, and as such it is recommended to avoid this for any non-trivial project)

var green_curry = require ('green_curry') (['short F.c'])
var f = green_curry.F.c ((x => `Hint: ${x}?`) >> green_curry.F.log)
f ('3') // prints 'Hint: 3?'

F (general functions and constants)

(note: regard the operators as prefix notation)

F.id : (x: 'a) -> 'a

The identity function

F.const : (x: 'a) -> unit -> 'a

Generates a constant function

F.ignore : (x: 'a) -> unit

Does nothing

F.exec : (f: unit -> 'a) -> 'a

Executes the given function

F.throw : (x: 'a) -> unit

Throw x

F.log : (x: 'a) -> unit

An alias for console.log.bind (console)

Aliasing and calling console.log by itself without binding will throw an exception

F.log_json : (x: 'a) -> (r: string array) -> (s: int) -> unit

Same as curried JSON.stringify and then console.log

F.= : (x: 'a) -> (y: 'a) -> bool

Deep comparison of x and y

F.== : (x: 'a) -> (y: 'a) -> bool

Compares the equality of x and y coerced to x's type

F.=== : (x: 'a) -> (y: 'a) -> bool

Compares the equality of x and y

F.!= : (x: 'a) -> (y: 'a) -> bool

F.<> : (x: 'a) -> (y: 'a) -> bool

Compares the inequality of x and y coerced to x's type

F.!== : (x: 'a) -> (y: 'a) -> bool

Compares the inequality of x and y

F.> : (x: num) -> (y: num) -> bool

Returns if x is greater than y

F.>= : (x: num) -> (y: num) -> bool

Returns if x is greater than or equal to y

F.< : (x: num) -> (y: num) -> bool

Returns if x is lesser than y

F.<= : (x: num) -> (y: num) -> bool

Returns if x is lesser than or equal to y

F.! : (x: bool) -> bool

Negates x

F.~ : (x: num) -> num

2's complements x

F.+ : (x: num) -> (y: num) -> num

Adds x by y

F.- : (x: num) -> (y: num) -> num

Subtracts x by y

F.* : (x: num) -> (y: num) -> num

Multiplies x by y

F./ : (x: num) -> (y: num) -> num

Divides x by y

F.% : (x: num) -> (y: num) -> num

Modulo divides x by y

F.| : (x: num) -> (y: num) -> num

Bitwise ors x by y

F.|| : (x: bool) -> (y: bool) -> bool

Logical ors x by y

(note: the arguments are evaluated eagerly so this does not short-circuit)

F.& : (x: num) -> (y: num) -> num

Bitwise ands x by y

F.&& : (x: bool) -> (y: bool) -> bool

Logical ands x by y

(note: the arguments are evaluated eagerly so this does not short-circuit)

F.^ : (x: num) -> (y: num) -> num

Bitwise xors x by y

F.>>> : (x: num) -> (y: num) -> num

Sign-propagating right shifts x by y

F.>>>> : (x: num) -> (y: num) -> num

Zero-fill right shifts x by y

F.<<< : (x: num) -> (y: num) -> num

Left shifts x by y

F.?? : (x: 'a) -> (y: 'a) -> 'a

Returns x if it is not null and y otherwise

(note: the arguments are evaluated eagerly so this does not short-circuit)

F.?: : (x: bool) -> (y: 'a) -> (z: 'a) -> 'a

Returns y if x and z otherwise

(note: the arguments are evaluated eagerly so this does not short-circuit)

F.|> : (x: 'a) -> (f: 'a -> 'b) -> 'b

F.@@ : (x: 'a) -> (f: 'a -> 'b) -> 'b

Calls f with x and returns the result

F.<| : (f: 'a -> 'b) -> (x: 'a) -> 'b

Calls f with x and returns the result

F.<< : (f: 'a -> 'b) -> (g: 'c -> 'a) -> ('c -> 'a)

Function composes f and g

F.>> : (f: 'a -> 'b) -> (g: 'c -> 'a) -> ('a -> 'c)

Reverse function composes f and g

F.neg : (f: 'a -> bool) -> ('a -> bool)

Returns a predicate that is the negation of f

F.union : (f: 'a -> bool) -> (g: 'a -> bool) -> ('a -> bool)

Returns a predicate that is a union of f and g

F.inter : (f: 'a -> bool) -> (g: 'a -> bool) -> ('a -> bool)

Returns a predicate that is an intersection of f and g

F.try

Calls the first function and returns the result

If an exception is thrown, passes it to the second function and returns the result

Essentially try/catch as an expression

F.try (() => {
  F.log ('Hint: 3?')
  throw new Error ()
})
.catch (err => {
  F.log (true)
}) // prints 'Hint: 3?' then true

F.swap : (f: 'a -> 'b -> 'c) -> ('b -> 'a -> 'c)

Swaps the order of the next two arguments of f

F.delay : (x: num) -> (f: unit -> unit) -> unit

Calls f after waiting x millisecons

F.tap : (f: 'a -> 'b) -> (x: 'a) -> 'a

Calls f with x and returns x

(note: for side-effecting when you want to retain the reference)

F.rcomp : (fs: (? -> ?) array) -> (? -> ?)

Reverse function composes the functions in fs

F.c : unit -> (fs: (? -> ?) array) -> (? -> ?)

Reverse function composes fs with a temporary DSL

(note: this is safe for nested usage in other instances of F.c and F.p)

(note: works by overriding Function.valueOf)

var f = F.c () (
    F.tap (F.log)
    >> F['='] ('Hint: 3?')
    >> F.tap (F.log)
)
f ('Hint: 3?') // true // prints 'Hint: 3?' then 'true'

F.p : (x: ?) -> (fs: (? -> ?) array) -> ?

Reverse function composes fs with a temporary DSL and calls that with x and returns the result

(note: this is safe for nested usage in other instances of F.c and F.p)

(note: works by overriding Function.valueOf)

F.p ('Hint: 3?') (
    F.tap (F.log)
    >> F['='] ('Hint: 3?')
    >> F.tap (F.log)
) == true // prints 'Hint: 3?' then 'true'

F.memoize : (f: 'a -> 'b) -> ('a -> 'b)

Returns a memoized version f

(note: the memoization has O(n) lookup)

F.times : (x: num) -> (f: unit -> unit) -> unit

Invokes f x times

F.after : (x: num) -> (f: 'a -> 'b') -> ('a -> unit/'b)

Returns a version of f that does nothing and returns undefined until the xth time when it reverts to normal

F.before : (x: num) -> (f: 'a -> 'b') -> ('a -> unit/'b)

Returns a version of f that operates normally until the xth time when it starts doing nothing and returns undefined

F.match

Returns the value of the first matching case

If no cases are matched and it is terminated with default, then the default case is executed

If no cases are matched and it is terminated with end, then an exception is thrown

F.match (3)
.case (1) (x => x + 5)
.case (2) (x => x + 4)
.case (3) (x => x)
.default (x => x + 3) == 3

F.match_f

Same as F.match, but takes predicate functions as cases

F.P (promise-based utility functions)

Promise-based utility functions are contained in the F.P submodule

F.P.try

Same as F.try, but supports promised functions

Only the expression as a whole returns a promise

await F.P.try (async () => {
  F.log ('Hint: 3?')
  throw new Error ()
})
.catch (async err => {
  F.log (true)
}) // prints 'Hint: 3?' then true

A (1 array and 2 arrays functions)

(note: arrays are assumed to be dense, meaning all data is contiguous)

(note: all respective orders are preserved, except in obvious cases)

1 array functions

A.clone : (xs: 'a array) -> 'a array

Returns a shallow copy of xs

A.cons : (x: 'a) -> (xs: 'a array) -> 'a array

Prepends x to l

A.head : (xs: 'a array) -> 'a

Throws an exception if xs is empty and returns the first element of xs otherwise

A.tail : (xs: 'a array) -> 'a array

Throws an exception if xs is empty and returns all elements of xs except the first otherwise

A.length : (xs: 'a array) -> num

Returns the length of l

A.is_empty : (xs: 'a array) -> bool

Returns whether the length of xs is 0

A.get : (x: num) -> (xs: 'a array) -> 'a

Returns the xth element in xs if it exists and returns undefined otherwise

A.range : (x: num) -> (y: num) -> num array

Returns the numbers between x and y, double inclusive, if x is less than or equal to y and an empty array otherwise

A.create : (x: num) -> (y: 'a) -> 'a array

Returns y repeated x times

A.init : (x: num) -> (f: int -> 'a) -> 'a array

Returns an array of x elements generated by f passed each index

A.rev : (xs: 'a array) -> 'a array

Returns xs with the elements in reverse order

A.iter : (f: 'a -> unit) -> (xs: 'a array) -> unit

Calls f on each element in l

A.iteri : (f: int -> 'a -> unit) -> (xs: 'a array) -> unit

Same as A.iter, except additionally passes the index as well

A.fold : (f: 'a -> 'b -> 'a) -> (a: 'a) -> (xs: 'b array) -> 'a

Calls f on the accumulator, initialized at a, and each element of xs and returns the result

A.reduce : (f: 'a -> 'a -> 'a) -> (xs: 'a array) -> 'a

Throws an exception is xs is empty and is the same as A.fold with the accumulator initialized to the first element in xs otherwise

A.scan : (f: 'a -> 'b -> 'a) -> (a: 'a) -> (xs: 'b array) -> 'a array

Same as A.fold, but additionally returns all of the partial sums

A.map : (f: 'a -> 'b) -> (xs: 'a array) -> 'b array

Returns xs with each element transformed by f

A.mapi : (f: int -> 'a -> 'b) -> (xs: 'a array) -> 'b array

Same as A.map, but additionally passes the index as well

A.find : (f: 'a -> bool) -> (xs: 'a array) -> 'a

Returns the first element in xs for which f returns true and throws an error if one does not exist

A.try_find : (f: 'a -> bool) -> (xs: 'a array) -> 'a

Returns the first element in xs for which f returns true and returns undefined if one does not exist

A.find_index : (f: 'a -> bool) -> (xs: 'a array) -> 'a

Returns the index of the first element in xs for which f returns true and throws an error if one does not exist

A.try_find_index : (f: 'a -> bool) -> (xs: 'a array) -> 'a

Returns the index of the first element in xs for which f returns true and returns undefined if one does not exist

A.pick : (f: 'a -> unit/'b) -> (xs: 'a array) -> unit/'b

Returns the result of f for the first element in xs for which f does not return undefined and throws an error if one does not exist

A.try_pick : (f: 'a -> unit/'b) -> (xs: 'a array) -> unit/'b

Returns the result of f for the first element in xs for which f does not return undefined and throws an error if one does not exist

A.filter : (f: 'a -> bool) -> (xs: 'a array) -> 'a array

Returns xs without the elements for which f returns false

A.for_all : (f: 'a -> bool) -> (xs: 'a array) -> bool

Returns if f is true for all elements in l, vacuously true

A.exists : (f: 'a -> bool) -> (xs: 'a array) -> bool

Returns if f is true for any element in l, vacuously false

A.contains : (x: 'a) -> (xs: 'a array) -> bool

Returns if any element in xs is equal to x

A.sort : (f: 'a -> 'a -> int) -> (xs: 'a array) -> 'a array

Returns xs sorted by f determined by normal comparator standards

A.partition : (f: 'a -> bool) -> (xs: 'a array) -> ('a array * 'a array)

Returns xs into two arrays, the first containing all elements for which f is true and the second containing everything else

A.uniq : (xs: 'a array) -> 'a array

Returns xs with duplicates removed

A.hash_uniq : (f: 'a -> string) -> (xs: 'a array) -> 'a array

Returns xs with duplicates removed according to the mapping through f

(note: this is substantially faster than A.uniq)

A.unzip : (xs: ('a * 'b) array) -> 'a array * 'b array

Returns the arrays of the first element of each element of xs and the second element of each element of l

(note: this function does not enforce density)

A.flatten: (xss: 'a array array) -> 'a array

Returns an array of xss flattened by one level

2 array functions

A.append : (l1: 'a array) -> (l2: 'a array) -> 'a array

Returns l1 prepended to l2

A.eq_length : (l1: 'a array) -> (l2: 'b array) -> bool

Returns if l1 and l2 have equal lengths

A.uneq_length : (l1: 'a array) -> (l2: 'b array) -> bool

Returns if l1 and l2 have unequal lengths

A.iter2 : (f: 'a -> 'b -> unit) -> (l1: 'a array) -> (l2: 'b array) -> unit

Throws exception F.e if l1 and l2 have unequal lengths and is the same as A.iter, but with corresponding elements of each array passed in otherwise

A.iteri2 : (f: int -> 'a -> 'b -> unit) -> (l1: 'a array) -> (l2: 'b array) -> unit

Same as A.iter2, except additionally passing the index

A.fold2 : (f: 'a -> 'b -> 'c -> 'a) -> (a: 'a) -> (l1: 'b array) -> (l2: 'c array) -> 'a

Throws exception F.e if l1 and l2 have unequal lengths and is the same as A.fold, but with corresponding elements of each array passed in otherwise

A.map2 : (f: 'a -> 'b -> 'c) -> (l1: 'a array) -> (l2: 'b array) -> 'c array

Throws exception F.e if l1 and l2 have unequal lengths and is the same as A.map, but with corresponding elements of each array passed in otherwise

A.mapi2 : (f: int -> 'a -> 'b -> 'c) -> (l1: 'a array) -> (l2: 'b array) -> 'c array

Throws exception F.e if l1 and l2 have unequal lengths and is the same as A.mapi, but with corresponding elements of each array passed in otherwise

A.for_all2 : (f: 'a -> 'b -> bool) -> (l1: 'a array) -> (l2: 'b array) -> bool

Throws exception F.e if l1 and l2 have unequal lengths and is the same as A.for_all, but with corresponding elements of each array passed in otherwise

A.exists2 : (f: 'a -> 'b -> bool) -> (l1: 'a array) -> (l2: 'b array) -> bool

Throws exception F.e if l1 and l2 have unequal lengths and is the same as A.exists, but with corresponding elements of each array passed in otherwise

A.zip : 'a array -> 'b array -> ('a * 'b) array

Throws exception F.e if l1 and l2 have unequal lengths and returns the corresponding elements of l1 and l2 combined

A.equals : (x: 'a array) -> (y: 'a array) -> bool

Deep comparison of x and y

A.P (promise-based array functions)

Promise-based array functions are contained in the A.P submodule, with A.P.s serial and A.P.p parallel submodules

Serial functions will operate in order and will reject on the first error or resolve on success

Parallel functions will initiate all operations at the same time and will resolve when all operations complete or reject on the first error before that

Not all functions are available in both modes

A.P.*.init : (x: num) -> (f: int -> 'a promise) -> 'a array promise

Returns an array of x elements generated by f passed each index

A.P.*.iter : (f: 'a -> unit promise) -> (xs: 'a array) -> unit promise

Calls f on each element in l

A.P.*.iteri : (f: int -> 'a -> unit promise) -> (xs: 'a array) -> unit promise

Same as A.iter, except additionally passes the index as well

A.P.s.fold : (f: 'a -> 'b -> 'a promise) -> (a: 'a) -> (xs: 'b array) -> 'a promise

Calls f on the accumulator, initialized at a, and each element of xs and returns the result

A.P.s.reduce : (f: 'a -> 'a -> 'a promise) -> (xs: 'a array) -> 'a promise

Throws an exception is xs is empty and is the same as A.fold with the accumulator initialized to the first element in xs otherwise

A.P.s.scan : (f: 'a -> 'b -> 'a promise) -> (a: 'a) -> (xs: 'b array) -> 'a array promise

Same as A.fold, but additionally returns all of the partial sums

A.P.*.map : (f: 'a -> 'b promise) -> (xs: 'a array) -> 'b array promise

Returns xs with each element transformed by f

A.P.*.mapi : (f: int -> 'a -> 'b promise) -> (xs: 'a array) -> 'b array promise

Same as A.map, but additionally passes the index as well

A.P.*.find : (f: 'a -> bool promise) -> (xs: 'a array) -> 'a promise

Returns the first element in xs for which f returns true and throws an error if one does not exist

A.P.*.try_find : (f: 'a -> bool promise) -> (xs: 'a array) -> 'a promise

Returns the first element in xs for which f returns true and returns undefined if one does not exist

A.P.*.pick : (f: 'a -> unit/'b promise) -> (xs: 'a array) -> unit/'b promise

Returns the result of f for the first element in xs for which f does not return undefined and throws an error if one does not exist

A.P.*.try_pick : (f: 'a -> unit/'b promise) -> (xs: 'a array) -> unit/'b promise

Returns the result of f for the first element in xs for which f does not return undefined and throws an error if one does not exist

A.P.*.filter : (f: 'a -> bool promise) -> (xs: 'a array) -> 'a array promise

Returns xs without the elements for which f returns false

A.P.*.for_all : (f: 'a -> bool promise) -> (xs: 'a array) -> bool promise

Returns if f is true for all elements in l, vacuously true

A.P.*.exists : (f: 'a -> bool promise) -> (xs: 'a array) -> bool promise

Returns if f is true for any element in l, vacuously false

D (dictionary functions)

D.is_empty : (d: 'a, 'b dictionary) -> bool

Returns if d is empty

D.get : (k: 'a) -> (d: 'a, 'b dictionary) -> 'b

Returns the element in d with key k

D.set : (k: 'a) -> (v: 'b) -> (d: 'a, 'b dictionary) -> 'a, 'b dictionary

Returns d with key k set to value v

D.create : (xs: ('a * 'b) array) -> 'a, 'b dictionary

Returns the dictionary with pairs of each element with the first element as the key and the second element as the value

D.keys : (d: 'a, 'b dictionary) -> 'a array

Returns the keys of d

D.vals : (d: 'a, 'b dictionary) -> 'b array

Returns the values of d

D.pairs : (d: 'a, 'b dictionary) -> ('a * 'b) array

Returns the key, value pairs of d

D.bind : (d: 'a, 'b dictionary) -> 'a, 'b dictionary

Binds the self-references for functions inside d to d and returns d

(note: this one of the gaps I mentioned in the opening; in languages that automatically resolve this problem, it's not an issue)

D.freeze : (d: 'a, 'b dictionary) -> 'a, 'b dictionary

Freezes d and returns d

D.freeze_bind : (d: 'a, 'b dictionary) -> 'a, 'b dictionary

Same as D.bind then D.freeze

D.iter : (f: 'a -> unit) -> (d: 'b, 'a dictionary) -> unit

Same as A.iter on the values of d, except with keys instead of indices

D.iterk : (f: 'a -> 'b -> 'c -> unit) -> (d: 'a, 'b dictionary) -> unit

Same as D.iter, except additionally passing the key

D.fold : (f: 'a -> 'b -> 'a) -> (a: 'a) -> (d: 'c, 'b dictionary) -> 'a

Same as A.fold on the values of d

D.foldk : (f: 'a -> 'b -> 'c -> 'a) -> (a: 'a) -> (d: 'b, 'c dictionary) -> 'a

Same as D.fold, except additionally passing the key

D.map : (f: 'a -> 'b) -> (d: 'c, 'a dictionary) -> 'c, 'b dictionary

Same as A.map on the values of d

D.mapk : (f: 'a -> 'b -> 'c) -> (d: 'a, 'b dictionary) -> 'a, 'c dictionary

Same as D.map, except additionally passing the key

D.find : (f: 'a -> bool) -> (d: 'b, 'a dictionary) -> 'a

Same as A.find on the values of d

D.findk : (f: 'a -> 'b -> bool) -> (d: 'a, 'b dictionary) -> 'b

Same as D.find, except additionally passing the key

D.filter : (f: 'a -> bool) -> (d: 'b, 'a dictionary) -> 'a array

Same as A.filter on the values of d

D.filterk : (f: 'a -> 'b -> bool) -> (d: 'a, 'b dictionary) -> 'a array

Same as D.filter, except additionally passed the key

D.for_all : (f: 'a -> bool) -> (d: 'b, 'a dictionary) -> bool

Same as A.for_all on the values of d

D.exists : (f: 'a -> bool) -> (d: 'b, 'a dictionary) -> bool

Same as A.exists on the values of d

D.contains : (x: 'a) -> (d: 'b, 'a dictionary) -> bool

Same as A.contains on the values of d

D.containsk : (x: 'a) -> (d: 'a, 'b dictionary) -> bool

Same as A.contains on the keys of d

D.length : (d: 'a, 'b dictionary) -> num

Returns the number of key, value pairs in d

D.partition : (f: 'a -> bool) -> (d: 'b, 'a dictionary) -> (('b, 'a) dictionary * ('b, 'a) dictionary)

Same as A.partition on the values of d

D.extend : (d1: 'a, 'b dictionary) -> (d2: 'a, 'b dictionary) -> 'a, 'b dictionary

Returns d1 overlaid by d2

D.copy : (xs: 'a array) -> (d: 'a, 'b dictionary) -> 'a, 'b dictionary

Returns d with only the pairs with keys in xs

D.delete : (xs: 'a array) -> (d: 'a, 'b dictionary) -> 'a, 'b dictionary

Returns d without the pairs with keys in xs

D.equals : (d1: ('a, 'b) dictionary) -> (d2: ('a, 'b) dictionary) -> bool

Deep comparison of d1 and d2

S (string functions)

S.length : (x: string) -> num

Returns the length of x

S.get : (x: num) -> (y: string) -> string

Returns the xth character in y

S.substr : (x: num) -> (y: num) -> (z: string) -> string

Returns the substring from x to y in z with some slice logic

S.index : (x: string) -> (y: string) -> num

Returns the first index at which x appears in y

S.contains : (x: string) -> (y: string) -> bool

Returns if x appears at least once in y

S.compare : (x: string) -> (y: string) -> num

Follows normal comparator rules for strings for comparing x to y

S.match : (r: regex) -> (x: string) -> string array

Returns the match and capture groups of r in x if x matches r and null otherwise

S.replace : (r: regex) -> (x: string) -> (y: string) -> string

Returns y with matches of r replaced by x

S.rindex : (x: string) -> (y: string) -> num

Same as S.index, except with the last occurence

S.search : (r: regex) -> (x: string) -> num

Returns the first index that x matches r

S.split : (r: regex) -> (x: string) -> string array

Returns a array of the substrings of x split by r

S.lower : (x: string) -> string

Returns x with all characters lowercase

S.upper : (x: string) -> string

Returns x with all characters uppercase

S.trim : (x: string) -> string

Returns x without surrounding whitespace

S.equals : (x: string) -> (y: string) -> bool

Returns if x and y are equal

S.join : (x: string) -> (xs: string array) -> string

Returns the string of all strings in xs with x in between each element