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
|
includes recursive definitions, nested function dependency cycles, and
|
||||||
uses of lambda functions. Some of the following examples will be rather
|
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
|
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