-
Notifications
You must be signed in to change notification settings - Fork 11
/
lambda-case.kl
95 lines (89 loc) · 3 KB
/
lambda-case.kl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#lang "prelude.kl"
(import (shift "prelude.kl" 1))
(import (shift "quasiquote.kl" 1))
(import "define-syntax-rule.kl")
(import "list.kl")
-- In Haskell, the LambdaCase language extension allows us to write
--
-- \case
-- [] -> "nil"
-- _:_ -> "cons"
--
-- instead of
--
-- \x -> case x of
-- [] -> "nil"
-- _:_ -> "cons"
--
-- Klister supports macros, meaning you can define this kind of syntactic
-- extensions yourself. Here is how to implement lambda-case as a macro.
-- (lambda-case -- (1)
-- [(nil) "nil"]
-- [(:: _ _) "cons"])
-- =>
-- (lambda (x) -- (2)
-- (case x
-- [(nil) "nil"]
-- [(:: _ _) "cons"]))
(define-variadic-macro (lambda-case stx)
(case (open-syntax stx) -- (3)
[(list-contents (:: _ cases))
(pure
`(lambda (x)
,(close-syntax stx stx -- (4)
(list-contents (:: 'case (:: 'x cases))))))]))
(example -- (5)
(let [f (lambda-case
[(nil) "nil"]
[(:: _ _) "cons"])]
(f (list 1 2 3))))
-- A macro is implemented as a function of type (-> Syntax (Macro Syntax)).
-- Macro is a monad, but we don't make use of its effects in this example, so
-- what matters is that we receive a Syntax and produce a Syntax. In the
-- example at (5), we receive (1) and produce (2).
--
-- Syntax objects will be covered in more details in a different example. For
-- now, it suffices to know that we can split a Syntax into a (List Syntax)
-- by using the open-syntax function and matching on the list-contents
-- constructor, as in (3), and we can combine a (List Syntax) back into a
-- Syntax by wrapping the (List Syntax) in that same list-contents constructor
-- and using the close-syntax function, as in (4).
--
-- In our running example, splitting (1) results in the list
--
-- (list 'lambda-case
-- '[(nil) "nil"]
-- '[(:: _ _) "cons"])
--
-- So the pattern (:: _ cases) drops 'lambda-case and keeps the list of cases.
--
-- This list of cases is then used to construct the longer list
--
-- (list 'case
-- 'x
-- '[(nil) "nil"]
-- '[(:: _ _) "cons"])
--
-- Which gets combined into
--
-- '(case x
-- [(nil) "nil"]
-- [(:: _ _) "cons"])
--
-- Which gets spliced into a bigger syntax object in order to produce the
-- desired output, (2).
--
-- You can see the result of running the example at (5) by running this file:
--
-- $ cabal run klister -- run examples/lambda-case.kl
-- Example at lambda-case.kl:42.1-97.1:
-- let f = λx. case x of { nil ↦ "nil" ; :: _ _ ↦ "cons" } in
-- (f (:: 1 (:: 2 (:: 3 nil)))) :
-- String ↦
-- "cons"
--
-- Or by looking at the golden file generated by our test suite:
--
-- $ cat examples/lambda-case.golden
-- "cons" : String
(export lambda-case)