From 971f58da9baf12faa626fd7b931d850b6c4e502f Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Sat, 20 Jun 2020 22:03:57 -0700 Subject: [PATCH] Finish draft of part 12 of compiler series. --- code/compiler/12/examples/fixpoint.txt | 17 +++ code/compiler/12/examples/lambda.txt | 19 +++ code/compiler/12/examples/letin.txt | 47 ++++++++ code/compiler/12/examples/packed.txt | 23 ++++ .../blog/12_compiler_let_in_lambda/index.md | 113 +++++++++++++++++- 5 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 code/compiler/12/examples/fixpoint.txt create mode 100644 code/compiler/12/examples/lambda.txt create mode 100644 code/compiler/12/examples/letin.txt create mode 100644 code/compiler/12/examples/packed.txt diff --git a/code/compiler/12/examples/fixpoint.txt b/code/compiler/12/examples/fixpoint.txt new file mode 100644 index 0000000..aba81e8 --- /dev/null +++ b/code/compiler/12/examples/fixpoint.txt @@ -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) } diff --git a/code/compiler/12/examples/lambda.txt b/code/compiler/12/examples/lambda.txt new file mode 100644 index 0000000..35deace --- /dev/null +++ b/code/compiler/12/examples/lambda.txt @@ -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))))) +} diff --git a/code/compiler/12/examples/letin.txt b/code/compiler/12/examples/letin.txt new file mode 100644 index 0000000..9e163b2 --- /dev/null +++ b/code/compiler/12/examples/letin.txt @@ -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)) + } +} diff --git a/code/compiler/12/examples/packed.txt b/code/compiler/12/examples/packed.txt new file mode 100644 index 0000000..8f25f71 --- /dev/null +++ b/code/compiler/12/examples/packed.txt @@ -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) + } + } +} diff --git a/content/blog/12_compiler_let_in_lambda/index.md b/content/blog/12_compiler_let_in_lambda/index.md index fc83037..23b111a 100644 --- a/content/blog/12_compiler_let_in_lambda/index.md +++ b/content/blog/12_compiler_let_in_lambda/index.md @@ -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!