Finish draft of part 12 of compiler series.
This commit is contained in:
@@ -827,4 +827,115 @@ It's important to test all the language features that we just added. This
|
||||
includes recursive definitions, nested function dependency cycles, and
|
||||
uses of lambda functions. Some of the following examples will be rather
|
||||
silly, but they should do a good job of checking that everything works
|
||||
as we expect.
|
||||
as we expect. Let's start with a simple use of a recursive definition
|
||||
inside a `let/in`. A classic definition in that form is of `fix`
|
||||
(the fixpoint combinator):
|
||||
|
||||
```Haskell
|
||||
fix f = let x = f x in x
|
||||
```
|
||||
|
||||
This defines `x` to be `f x`, which by substitution becomes `f (f x)`, and then
|
||||
`f (f (f x))` and so on. The fixpoint combinator allows one to write a
|
||||
recursive function that doesn't use its own name in the body. Rather,
|
||||
we write a function expecting to receive 'itself' as a value:
|
||||
|
||||
```Haskell
|
||||
fix :: (a -> a) -> a
|
||||
|
||||
factRec :: (Int -> Int) -> Int -> Int
|
||||
factRec f x = if x == 0 then 1 else x * f x
|
||||
|
||||
fact :: Int -> Int
|
||||
fact = fix factRec
|
||||
```
|
||||
|
||||
Notice that `factRec` doesn't reference itself, but rather takes
|
||||
as argument a function it expects to be 'factorial' called `f`,
|
||||
and uses that in its recursive case. We can write something similar
|
||||
in our language, perhaps to create an infinite list of ones:
|
||||
|
||||
{{< codeblock "text" "compiler/12/examples/fixpoint.txt" >}}
|
||||
|
||||
We want `sumTwo` to take the first two elements from the list,
|
||||
and return their sum. For an infinite list of ones, we expect
|
||||
this sum to equal to 2, and so it does:
|
||||
|
||||
```
|
||||
Result: 2
|
||||
```
|
||||
|
||||
Next, let's try to define a function which has a mutually recursive pair
|
||||
of definitions inside of a `let/in`. Let's also make these expressions
|
||||
reference a function from the global scope, so that we know our
|
||||
dependency tracking works as expected:
|
||||
|
||||
{{< codeblock "text" "compiler/12/examples/letin.txt" >}}
|
||||
|
||||
Here, we have a function `mergeUntil` which, given two lists
|
||||
and a predicate, combines the two lists until as long as
|
||||
the predicate returns `True`. It does so using a convoluted
|
||||
pair of two mutually recursive functions, one of which
|
||||
unpacks the left list, and the other the right. Each of the
|
||||
functions calls the global function `if`. We also use two
|
||||
definitions inside of `main` to create the two lists we're
|
||||
going to merge. The compiler outputs the following (correct)
|
||||
types:
|
||||
|
||||
```
|
||||
const: forall bb bc . bc -> bb -> bc
|
||||
if: Bool* -> List* Int* -> List* Int* -> List* Int*
|
||||
main: Int*
|
||||
mergeUntil: List* Int* -> List* Int* -> (Int* -> Bool*) -> List* Int*
|
||||
sum: List* Int* -> Int*
|
||||
```
|
||||
|
||||
And the result is 21, as would be expected from the sum of the numbers 1-6:
|
||||
|
||||
```
|
||||
Result: 21
|
||||
```
|
||||
|
||||
Let's try lambda functions now. We can try use them for a higher-order function
|
||||
like `map`:
|
||||
|
||||
{{< codeblock "text" "compiler/12/examples/lambda.txt" >}}
|
||||
|
||||
In this example, we first double every element in the list, then square it,
|
||||
and finally take the sum. This should give us 4+16+36 = 56, and so it does:
|
||||
|
||||
```
|
||||
Result: 56
|
||||
```
|
||||
|
||||
Finally, let's do some magic with a locally-declared data type. We'll make a
|
||||
"packer" that creates a wrapped instance of a type, `Packed a`. Since the
|
||||
constructor of this data type is not globally visible, it's not possible
|
||||
to get the value back out, except by using an 'unpacking' function that
|
||||
we provide:
|
||||
|
||||
{{< codeblock "text" "compiler/12/examples/packed.txt" >}}
|
||||
|
||||
Here, the `packer` definition returns a pair of the 'packing'
|
||||
and 'unpacking' functions. The 'packing' function simply applies
|
||||
the consntructor of `Packed` to its argument, while the 'unpacking'
|
||||
function performs pattern matching (which is possible since the
|
||||
data type is still in scope there). We expect `unpack (pack 3)` to
|
||||
return 3, and it does:
|
||||
|
||||
```
|
||||
Result: 3
|
||||
```
|
||||
|
||||
Trying to pattern match, though, doesn't work, just like we would want!
|
||||
|
||||
This is enough to convince me that our changes do, indeed, work! Of
|
||||
the 'major' components that I wanted to cover, only __Input/Output__
|
||||
remains! Additionally, a [lobste.rs](https://lobste.rs) user suggested
|
||||
that we also cover namespacing, and perhaps we will look into that as well.
|
||||
Before either of those things, though, I think that I want to go through
|
||||
the compiler and perform another round of improvements, similarly to
|
||||
[part 4]({{< relref "04_compiler_improvements" >}}). It's hard to do a lot
|
||||
of refactoring while covering new content, since major changes need to
|
||||
be explained and presented for the post to make sense. I hope to see
|
||||
you in these future posts!
|
||||
|
||||
Reference in New Issue
Block a user