Start work on part 12 of compiler series
Some checks failed
continuous-integration/drone/push Build is failing
BIN
content/blog/12_compiler_let_in_lambda/fig_colored.png
Executable file
After Width: | Height: | Size: 396 KiB |
BIN
content/blog/12_compiler_let_in_lambda/fig_colored_ordered.png
Executable file
After Width: | Height: | Size: 457 KiB |
BIN
content/blog/12_compiler_let_in_lambda/fig_graph.png
Executable file
After Width: | Height: | Size: 117 KiB |
BIN
content/blog/12_compiler_let_in_lambda/fig_subgraphs.png
Executable file
After Width: | Height: | Size: 137 KiB |
BIN
content/blog/12_compiler_let_in_lambda/fig_subgraphs_colored.png
Executable file
After Width: | Height: | Size: 195 KiB |
BIN
content/blog/12_compiler_let_in_lambda/fig_subgraphs_colored_all.png
Executable file
After Width: | Height: | Size: 471 KiB |
BIN
content/blog/12_compiler_let_in_lambda/fig_subgraphs_colored_ordered.png
Executable file
After Width: | Height: | Size: 602 KiB |
85
content/blog/12_compiler_let_in_lambda/index.md
Normal file
|
@ -0,0 +1,85 @@
|
|||
---
|
||||
title: Compiling a Functional Language Using C++, Part 12 - Let/In and Lambdas
|
||||
date: 2020-04-20T20:15:16-07:00
|
||||
tags: ["C and C++", "Functional Languages", "Compilers"]
|
||||
draft: true
|
||||
---
|
||||
|
||||
Now that our language's type system is more fleshed out and pleasant to use,
|
||||
it's time to shift our focus to the ergonomics of the language itself. I've been
|
||||
mentioning `let/in` expressions and __lambda expressions__ for a while now.
|
||||
The former will let us create names for expressions that are limited to
|
||||
a certain scope (without having to create global variable bindings), while
|
||||
the latter will allow us to create functions without giving them any name at
|
||||
all.
|
||||
|
||||
Let's take a look at `let/in` expressions first, to make sure we're all on
|
||||
the same page about what it is we're trying to implement. Let's
|
||||
start with some rather basic examples, and then move on to more
|
||||
complex examples. The most basic use of a `let/in` expression is, in Haskell:
|
||||
|
||||
```Haskell
|
||||
let x = 5 in x + x
|
||||
```
|
||||
|
||||
In the above example, we bind the variable `x` to the value `5`, and then
|
||||
refer to `x` twice in the expression after the `in`. The whole snippet is one
|
||||
expression, evaluating to what the `in` part evaluates to. Additionally,
|
||||
the variable `x` does not escape the expression -
|
||||
{{< sidenote "right" "used-note" "it cannot be used anywhere else." >}}
|
||||
Unless, of course, you bind it elsewhere; naturally, using <code>x</code>
|
||||
here does not forbid you from re-using the variable.
|
||||
{{< /sidenote >}}
|
||||
|
||||
Now, consider a slightly more complicated example:
|
||||
|
||||
```Haskell
|
||||
let sum xs = foldl (+) 0 xs in sum [1,2,3]
|
||||
```
|
||||
|
||||
Here, we're defining a _function_ `sum`,
|
||||
{{< sidenote "right" "eta-note" "which takes a single argument:" >}}
|
||||
Those who favor the
|
||||
<a href="https://en.wikipedia.org/wiki/Tacit_programming#Functional_programming">point-free</a>
|
||||
programming style may be slightly twitching right now, the words
|
||||
<em>eta reduction</em> swirling in their mind. What do you know,
|
||||
<code>fold</code>-based <code>sum</code> is even one of the examples
|
||||
on the Wikipedia page! I assure you, I left the code as you see it
|
||||
deliberately, to demonstrate a principle.
|
||||
{{< /sidenote >}} the list to be summed. We will want this to be valid
|
||||
in our language, as well. We will soon see how this particular feature
|
||||
is related to lambda functions, and why I'm covering these two features
|
||||
in the same post.
|
||||
|
||||
Let's step up the difficulty a bit more, with an example that,
|
||||
{{< sidenote "left" "translate-note" "though it does not immediately translate to our language," >}}
|
||||
The part that doesn't translate well is the whole deal with patterns in
|
||||
function arguments, as well as the notion of having more than one equation
|
||||
for a single function, as is the case with <code>safeTail</code>.
|
||||
<br><br>
|
||||
It's not that these things are <em>impossible</em> to translate; it's just
|
||||
that translating them may be worthy of a post in and of itself, and would only
|
||||
serve to bloat and complicate this part. What can be implemented with
|
||||
pattern arguments can just as well be implemented using regular case expressions;
|
||||
I dare say most "big" functional languages actually just convert from the
|
||||
former to the latter as part of the compillation process.
|
||||
{{< /sidenote >}} illustrates another important principle:
|
||||
|
||||
```Haskell
|
||||
let
|
||||
safeTail [] = Nothing
|
||||
safeTail [x] = Just x
|
||||
safeTail (_:xs) = safeTail xs
|
||||
myTail = safeTail [1,2,3,4]
|
||||
in
|
||||
myTail
|
||||
```
|
||||
|
||||
The principle here is that definitions in `let/in` can be __recursive and
|
||||
polymorphic__. Remember the note in
|
||||
[part 10]({{< relref "10_compiler_polymorphism.md" >}}) about
|
||||
[let-polymorphism](https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system#Let-polymorphism)? This is it: we're allowing polymorphic variable bindings,
|
||||
but only when they're bound in a `let/in` expression (or at the top level).
|
||||
|
||||
The principles demonstrated by the last two snippets mean that compiling `let/in`
|
||||
expressions, at least with the power we want to give them
|