Finish draft of part 12 of compiler series.
This commit is contained in:
parent
c496be1031
commit
971f58da9b
17
code/compiler/12/examples/fixpoint.txt
Normal file
17
code/compiler/12/examples/fixpoint.txt
Normal file
|
@ -0,0 +1,17 @@
|
|||
data List a = { Nil, Cons a (List a) }
|
||||
|
||||
defn fix f = { let { defn x = { f x } } in { x } }
|
||||
defn fixpointOnes fo = { Cons 1 fo }
|
||||
defn sumTwo l = {
|
||||
case l of {
|
||||
Nil -> { 0 }
|
||||
Cons x xs -> {
|
||||
x + case xs of {
|
||||
Nil -> { 0 }
|
||||
Cons y ys -> { y }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defn main = { sumTwo (fix fixpointOnes) }
|
19
code/compiler/12/examples/lambda.txt
Normal file
19
code/compiler/12/examples/lambda.txt
Normal file
|
@ -0,0 +1,19 @@
|
|||
data List a = { Nil, Cons a (List a) }
|
||||
|
||||
defn sum l = {
|
||||
case l of {
|
||||
Nil -> { 0 }
|
||||
Cons x xs -> { x + sum xs}
|
||||
}
|
||||
}
|
||||
|
||||
defn map f l = {
|
||||
case l of {
|
||||
Nil -> { Nil }
|
||||
Cons x xs -> { Cons (f x) (map f xs) }
|
||||
}
|
||||
}
|
||||
|
||||
defn main = {
|
||||
sum (map \x -> { x * x } (map (\x -> { x + x }) (Cons 1 (Cons 2 (Cons 3 Nil)))))
|
||||
}
|
47
code/compiler/12/examples/letin.txt
Normal file
47
code/compiler/12/examples/letin.txt
Normal file
|
@ -0,0 +1,47 @@
|
|||
data Bool = { True, False }
|
||||
|
||||
data List a = { Nil, Cons a (List a) }
|
||||
|
||||
defn if c t e = {
|
||||
case c of {
|
||||
True -> { t }
|
||||
False -> { e }
|
||||
}
|
||||
}
|
||||
|
||||
defn mergeUntil l r p = {
|
||||
let {
|
||||
defn mergeLeft nl nr = {
|
||||
case nl of {
|
||||
Nil -> { Nil }
|
||||
Cons x xs -> { if (p x) (Cons x (mergeRight xs nr)) Nil }
|
||||
}
|
||||
}
|
||||
defn mergeRight nl nr = {
|
||||
case nr of {
|
||||
Nil -> { Nil }
|
||||
Cons x xs -> { if (p x) (Cons x (mergeLeft nl xs)) Nil }
|
||||
}
|
||||
}
|
||||
} in {
|
||||
mergeLeft l r
|
||||
}
|
||||
}
|
||||
|
||||
defn const x y = { x }
|
||||
|
||||
defn sum l = {
|
||||
case l of {
|
||||
Nil -> { 0 }
|
||||
Cons x xs -> { x + sum xs }
|
||||
}
|
||||
}
|
||||
|
||||
defn main = {
|
||||
let {
|
||||
defn firstList = { Cons 1 (Cons 3 (Cons 5 Nil)) }
|
||||
defn secondList = { Cons 2 (Cons 4 (Cons 6 Nil)) }
|
||||
} in {
|
||||
sum (mergeUntil firstList secondList (const True))
|
||||
}
|
||||
}
|
23
code/compiler/12/examples/packed.txt
Normal file
23
code/compiler/12/examples/packed.txt
Normal file
|
@ -0,0 +1,23 @@
|
|||
data Pair a b = { Pair a b }
|
||||
|
||||
defn packer = {
|
||||
let {
|
||||
data Packed a = { Packed a }
|
||||
defn pack a = { Packed a }
|
||||
defn unpack p = {
|
||||
case p of {
|
||||
Packed a -> { a }
|
||||
}
|
||||
}
|
||||
} in {
|
||||
Pair pack unpack
|
||||
}
|
||||
}
|
||||
|
||||
defn main = {
|
||||
case packer of {
|
||||
Pair pack unpack -> {
|
||||
unpack (pack 3)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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!
|
||||
|
|
Loading…
Reference in New Issue
Block a user