Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Thanks for sharing!

  ; these produce an error, since `b` isn't defined when the body of `a` is compiled
  let a = \x -> (b x * 3),
      b = \x -> (a x / 2)
It surprised me when this was called out, given that both a and b are defined in the one 'let'. Was there a specific reason you decided not to treat it as a 'letrec'?


Yeah I went back and forth on this a little bit. If all variables are defined at the beginning of the `let` expression, you can't rebind a variable like this (assuming some previous `x`):

    let x = x + 1
because the new `x` shadows the old one before `x + 1` is compiled. But if you're defining a recursive function, like this:

    let foo = \n -> foo(n + 1)
you need `foo` to be defined before you compile the lambda body, or else the compiler will think you're referencing an undefined variable.

At one point I had an exception where, if the value of a variable being defined is a lambda, it defines the variable first to allow recursion, but not otherwise. But this felt inconsistent and kind of gross. Instead, I decided to have `def` expressions behave like that, and disallow recursion in `let`. `def` is always a function definition, so you'd almost always want that behavior, and I felt that since it has a different syntax, slightly different assignment semantics wouldn't be so bad.

For mutual recursion you have to go a little further, and find all the definitions in the block before you start compiling anything. So `def` expressions are also hoisted to the top of the block and pre-defined at the beginning. This felt ok to me since `def` expressions look like big, formal, static definitions anyway, and it didn't seem surprising that they would have whole-block scope.


For my hobby language, I figured let rec should be the default and let nonrec the marked case, for exactly the rebinding application. However, it's been over a year since I came to that conclusion, and I still haven't gotten around to implementing the nonrecursive path. (but: mine is very no-moving-parts Immutable, so ymmv)


By the way, why do you need a backslash to define a lambda? Apparently it doesn't give any additional information. All you need to know that's a lambda is the presence of the -> operator. Is that a way to make the compiler faster?


That was a pretty late change to the syntax actually — I really, really wanted Javascript-style lambdas but with a skinny arrow, like `(x, y) -> x + y`. But it made parsing and compiling really finicky, so I settled on the backslash syntax, which I've seen in a couple other languages. It almost looks like a "λ"!


Alternatively, we can go the other way, and dispense with the arrow:

  \x \y x + y
rather closer to the lambda notation in maths, and works well for compact expressions such as S from the SKI combinator calculus:

  \x \y \z x z (y z)
This looks much better with syntax highlighting (hard to demonstrate here of course), being both trivial to implement and informative - just have the back slashed tokens in blue or whatever.

Cassette looks really nice - great intro page, and a great name too! Making something simple is much harder and more valuable than making something complicated.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: