mattbierner / khepri

ECMAScript derived programming language
http://khepri-lang.com/
MIT License
67 stars 3 forks source link

Macroish Functions (Call by need) #122

Open mattbierner opened 9 years ago

mattbierner commented 9 years ago

Inlining framework already has most of the code in place to support a simple macro type system that uses call be need evaluation. This is required in order to allow programers to write code that can express the same logic as the builtin && and || ops.

Potential syntax (using hamt code example):

// Current pattern, repeated many times in code
?isNothing v
     :empty
     :new Leaf(h, k, v)

We can't create a helper function maybe for this because even if v is nothing, the new Leaf(h, k, v) arg is evaluated.

var maybe := \ val con alt  ->
    ?isNothing val : alt : con;

maybe(v,  new Leaf(h, k, v), empty);

Using call by need instead:

static maybe \ val con alt  ->
    ?isNothing val : alt : con;

maybe(v,  new Leaf(h, k, v), empty);

Expands to:

?isNothing (v)
    :(empty)
    :(new Leaf(h, k, v)); 

Using static for call by need, each argument must be a valid expression and the body must be a valid expression on its own. But instead of binding values to arguments, the body is rewritten at compile time to replace instances of identifies with the appropriate sub expression.

Static may be convertible to regular functions, at which point it would revert to using call by value instead.

mattbierner commented 9 years ago

Nevermind. That's a stupid approach.

Instead add two operators (placeholder ops used here until finalized):

\\ - Create thunk # - Eval Thunk

where:

\\ is a macroish unary operator that takes an expression and puts it into a thunk.

and # is a regular unary operator that does:

var (#) := \x -> ?isThunk x : x() : x;

Together these can be used to write call be need expression that inlining can then optimize to eliminate the overhead in normal cases.

var maybe := \ val con alt  ->
    ?isNothing val : #alt : #con;

maybe(v, \\new Leaf(h, k, v), empty);

expands to

var maybe := \ val con alt  ->
    ?isNothing val : #alt : #con;

maybe(v,  \-> new Leaf(h, k, v), empty);

Inlines to:

var maybe := \ val con alt  ->
    ?isNothing val : #alt : #con;

let val = v, con = \-> new Leaf(h, k, v), alt = empty in ?
      ?isNothing val : #alt : #con;

Inline to:

let val = v, alt = \-> new Leaf(h, k, v), con = empty in ?
      ?isNothing val : (?isThunk con : con() :con) : (?isThunk alt : alt() : alt);

const prop to:

let alt = \-> new Leaf(h, k, v) in ?
      ?isNothing v :(?isThunk empty : empty() :empty) : alt();

Inline to:

      ?isNothing v :(?isThunk empty : empty() :empty) : new Leaf(h, k, v);