-
Notifications
You must be signed in to change notification settings - Fork 5
authoring reference
import {assert,config,LC,getSolution} from "./lc-test.js"
imports 6
things:
-
chai.assert
asassert
-
chai.config
asconfig
LC = { compile, configure, fromInt, toInt, Primitive }
-
getSolution
,getPreloaded
andgetSolutionWithPreloaded
getSolution()
returns the user Solution
code, getPreloaded
returns the author Preloaded
code, getSolutionWithPreloaded()
returns a safe version of Preloaded
and Solution
code, concatenated.
LC.compile(getSolution())
( or a variant ) returns a JavaScript Object
with all top level definitions from the compiled Lambda Calculus source code, which can be destructured.
const solution = LC.compile(getSolution());
const { "is-empty": isEmpty } = solution;
Destructuring can be performed multiple times to avoid overly long lines, and keys can be renamed to comply with JavaScript identifier name restrictions.
Note that definitions can be overwritten; for multiply defined names only the last definitions will be exported, but other definitions always use the definition of a name at the time of their own definition.
Because solvers can also overwrite Preloaded
definitions, do not rely on solver exports from LC.compile(getSolutionWithPreloaded())
; always ( ALWAYS! ) get those exports directly from LC.compile(getPreloaded())
, or define them as LC-encoded JS values in Testing
.
Default configuration is
{ verbosity: "Calm" // Calm | Concise | Loquacious | Verbose
, purity: "PureLC" // Let | LetRec | PureLC
, numEncoding: "None" // None | Church | Scott | BinaryScott
, enforceBinaryScottInvariant: true // Boolean
}
LC.configure
is called with a JavaScript Object
with the desired new setting(s), eg. configure({ purity: "LetRec" })
. Unnamed settings are unchanged.
Currently, solvers can only change the configuration for Example
testing, by editing the test suite directly. A syntax extension with compiler pragmas to allow solvers access to configuration is being considered; there will then also be configuration options to forbid particular configuration changes by pragma ( you probably don't want solvers to change numEncoding
).
verbosity
controls the amount of debug and error information displayed. It is recommended to set verbosity
to at least Concise
for Submit
testing.
purity
controls both if defined names can be used in later definitions ( in PureLC
, they cannot; in Let
and LetRec
, they can ), and if self-recursion is allowed in definitions ( only in LetRec
). In PureLC
, solvers will have to apply lambda abstractions to define names for their own use. Below LetRec
, solvers will have to implement recursion themselves explicitly.
numEncoding
controls what number encoding is transparently used by the compiler. This may also be used in assert.equal
but will not be recognised by assert.deepEqual
. In general, authors will have to encode and decode values other than numbers ( booleans, arrays, tuples ) explicitly. There are currently no plans to support any values other than numbers in the compiler syntax.
enforceBinaryScottInvariant
controls if toInt
checks BinaryScott numbers for zero-padding in violation of the invariant that the MSB, if any, is a 1
. This is ON
by default. Note that solutions can use padded numbers internally; the check occurs only when a number is tested, ie. exported to JavaScript. No other number encodings with invariants have been defined ( yet ), so no checking is done for them.
fromInt
is called with a JavaScript Number
and returns a Lambda Calculus expression for the number, in the active encoding ( configure().numEncoding
), compiled to a JavaScript Function
.
toInt
is called with a JavaScript Function
equivalent to a Lambda Calculus expression, and returns a JavaScript Number
for the expression, decoded according to the active encoding.
These functions are used by the compiler itself for transparently compiling JavaScript Number
s in Lambda Calculus source code, but they can be called directly from testing code.
Invoking configure({ numEncoding: { fromInt() {}, toInt() {} } });
allows an author to specify a custom number encoding. This encoding will then also be used by the compiler.
JavaScript Number
s ( not BigInt
s ) that are ( in testing ) passed to compiled LC functions will automatically be converted to LC values according to the numEncoding
in effect. This behaviour can be disabled by wrapping the Number
in Primitive
, eg. ifThenElse (boolean) ((LC.Primitive(1)) ((LC.Primitive(0))
will return a JS Number
; without the wrapper, it would return a numEncoded LC value.
assert
is standard chai.assert
extended with assert.numEql
.
assert.numEql(actual,expected,message)
will automatically decode ( according to the active numEncoding
) actual
to a Javacript Number
for comparison to expected
and failure messaging, obviating any need for explicit decoding.
assert.equal, .strictEqual, .deepEqual
, with explicit decoding, are still to be used for comparing values of other types.
Kata author Preloaded
code and solver Solution
code are String
s in Lambda Calculus syntax. Separately compiling Preloaded
code means its definitions are inaccessible to Solution
code and is probably abuse of the Preloaded
mechanism; this code should be in Testing
, possibly precompiled. Explicitly importing Preloaded
definitions into Solution
code ( with a compiler pragma ) is under consideration as an extension of the current syntax.
LC.compile
compiles Lambda Calculus code to JavaScript Function
s. There is a one-to-one correspondence between Lambda Calculus syntax and JavaScript syntax, and everything that comes out of compile
is JavaScript, and is a callable Function
. It can be applied with native JavaScript Function
s, with compiled Lambda Calculus terms ( thus JavaScript Function
s ), or with other JavaScript values, which will pass uncompiled. Thus, everything that happens in testing happens in JavaScript.
The only way to arrive at JavaScript values from compiled Lambda Calculus code is passing its compiled form a JavaScript value in testing; there is no way to embed JavaScript values in Lambda Calculus code - not even passing it Number
s, which will be transparently encoded ( or generate an EvalError
, when numEncoding = None
).
Note that there are no String
s in Lambda Calculus syntax, though you can pass and manipulate String
s in testing ( ie. in JavaScript ).
Also note that all passed Number
s will be transparently encoded, which will generate a RangeError
for negative numbers in most numEncoding
s. Number
s wrapped in Primitive
( qv. ) will be passed unchanged.
numEncoding
is designed to set a particular numeral encoding for the kata, and provide support for testing with that. Other encodings however should be as free for solvers as possible. Use constructors and deconstructors in wrappers for testing, and allow solvers to export their own (de)constructors for datatypes other than numbers.
For booleans, ask for False, True : Boolean
and if : Boolean -> z -> z -> z
( \ if (boolean) (true) (false) . result
).
For 2-tuples, ask for Pair : x -> y -> Pair x y
and fst : Pair x y -> x, snd : Pair x y -> y
.
For lists, ask for nil : List x, cons : x -> List x -> List x
and foldr : (x -> z -> z) -> z -> List x -> z
.
For streams, ask for cons : x -> Stream x -> Stream x
and head : Stream x -> x, tail : Stream x -> Stream x
.
For option types, ask for none : Option x, some : x -> Option x
and option : z -> (x -> z) -> Option x -> z
.
E.g.
const { foldr } = solution;
const toArray = xs => foldr ( x => xs => [x,...xs] ) ( [] ) (xs) ;
xs
is an LC value here; toArray(xs)
is then a JS array that can be tested ( don't forget the elements are encoded as well! ).
Refer to the Encodings Reference for naming conventions.