busterjs / referee-combinators

Combinators goes assertion
0 stars 1 forks source link

What to do with half-finished "CL 101"? #8

Open meisl opened 11 years ago

meisl commented 11 years ago

Remember when I announced a "101" on Combinatory Logic in https://github.com/busterjs/buster/issues/339 ([1] here)?

I have a draft lying around half-finished which I wasn't pleased with because it's already quite lengthy. I found myself striking out ever farther... and just stopped at some point. But then I thought it'd be a pity if it were just thrown away.

We do have a bit of confusion of terms re combinators: what are the things that are being combined vs those that do the combining (the real combinators in my view)? So that was the main motivation to write it. Not sure if it's helpful though, or rather creates more confusion...

Anyways, don't wanna just throw it away. So at least it's here. Perhaps put it into git, docs/ or so?

Here's what I got, with a lot of "TODO"s:


λ & CL 101

Intro

"Lambda calculus" - at least as a term or name - is probably known to everyone, and so is Alonzo Church, who devised the thing in 1930. Not so well-known is, on the other hand, "Combinatory Logic" (CL) which was invented in 1920, by a Ukrainian named Moses Schönfinkel. It turned out later that both systems have precisely the same power of expressiveness. Schönfinkel, however, didn't publish a lot[1] and died in 1942. It took another American to re-invent combinators in 1927 (who had not seen Schönfinkel's paper then): Haskell Curry. You definitely have heard of "Haskell" and most probably you know what "currying"[2] is, don't you?

λ syntax

The syntax of the lambda calculus ("λ calculus" or even just "λ" for short) is nothing but an extremely compact way of writing down functions. It's even so compact that no function ever gets a name, pretty much like anonymous function expressions in JS. So eg. function (x) { return x; } (the identity function) would translate to λ as \x.x. Formally, there are only three basic entities in λ's syntax:

That's it :) Well ok, the basic entities as such are not what really makes up the language of λ. Rather, a formal language - such as λ - is nothing but a set, and the elements are called terms. These, in turn, are composed of the basic entities. For λ it goes like this:

Examples: neither \ alone nor \x are valid λ terms, whereas x alone or x x or \x.x are.

We can (and will) of course use abbreviations in order to reduce clutter. For example we might say I\x.x once and for all, and thereafter only use the one character I in place of the previous four. We'll reserve upper-case latin letters for such abbreviations[5].

[TODO: I in JS]
Currying

Abstraction from above only gives rise to unary (one-argument) functions, so what about binary (2-arg), ternary (3-arg), ...? Well, due to the magic of "currying"/"schönfinkeling" \x y.T (binary) can be viewed as a mere abbreviation for \x.(\y.T), and similarly \x y z.T (ternary) for \x.(\y.(\z.T)), and so on... Why that is safe (ie always works) is beyond scope here - but it does. Really :)

[TODO: no variadic fns in lambda, not a good idea to mix currying and variadic fns]
Scope and free vs bound variables
[TODO: define "scope", "binding" and "bound"]

In lambda, a combinator is simply a closed term (one where all variables are bound). The "postpone-the-actual" operation that defert

[TODO: no such thing as defert anymore!]

does[1] is such a combinator: \x y z.x z y where \ stands for the greek letter lambda λ. It is known as the combinator C. In JS it is

function defert (assertion expected) {
    return function (actual) {
        return assertion.apply(null, actual, expected);
    };
}

and in Haskell it would read

defert :: (a -> e -> Bool) -> e -> (a -> Bool)
defert assertion expected = \actual -> assertion actual expected

(the parens around a -> Bool in the type decl aren't necessary) Note that I chose the type Bool instead of a type variable, in order to emphasize the decision-making character of an assertion. Such a (systematic) specialization is not possible in JS, nor in pure λ.

[TODO: Scala example]

Some more combinators with standard names:

Now, in CL you have nothing but combinators! And yet it is equivalent to lambda calculus.

[TODO: rephrase]

Of course we can't use λ (nor .) for definitions since it's simply not part of the language. Rather it goes like this:

...and that's all for the syntax of CL.

[TODO: example CL terms]
λ and CL computations
[TODO: variable substitution (short! but mention de-Bruyn), "alpha-conversion"]
[TODO: define "contraction", (beta-)"reduction", mention Church-Rosser]
[TODO: ex derivations in both λ and CL: B from S&K, C from S&K&B]
[TODO: I from S&K? (minimal base)]
[TODO: R? Y? D/D1,D2? (pairing/projection) In any case: Smullyan]
Mapping back to referee-combinators
[TODO: derive general argument-permutation combinator]
[TODO: derive others...]
Further reading
[TODO: Hindley/Seldin, maybe Barendregt]
[TODO: Smullyan]
[TODO: Hutton (parser combinators)]
[TODO: the blog about parser combinators in Scala]

[1]: In fact it was a colleague who wrote the only paper on it [

[TODO: ref!]

] in 1924, from talks that Schönfinkel had given. [2]: Curry always insisted on that he had gotten the idea from Schönfinkel. We can only guess why it's not called "schönfinkeling"... [3]: precisely: symbols for variables - but don't be put off if you don't get the distinction immediately, it's rather philosophical and of no importance here [4]: Note they are upper-case. That's because they're variables in the meta-language, ie. they stand for whatever term from the object language (which is λ here). [5]: Note however, that strictly speaking I is NOT in the language of λ itself. Rather it's part of a meta-language (the language that's used to speak about the λ language). In fact, this is the very reason for keeping lower-case variable (names) separated from upper-case ones.

meisl commented 11 years ago

This should be tagged "question", seems I can't do it.