gdritter repos documents / 97addfa
some posts Getty Ritter 9 years ago
4 changed file(s) with 292 addition(s) and 0 deletion(s). Collapse all Expand all
1 Years ago, I worked at the [Berkeley Self-Paced Center], which is a
2 wonderful institution. It was designed to be a self-directed,
3 one-on-one center where people could go to learn a new programming
4 language (or sometimes programming environment) in a structured way
5 but at the student's own pace[^1].
6
7 [^1]: At least, nominally; in practice, the large influx of students
8 at the end of the semester—who had forgotten about their enrollment
9 until on the verge of failing—led us to experiment with various
10 ways of imposing a loose schedule on students, so that
11 we could try to even out the frequency of student visits.
12
13 One of the early assignments in these courses I really liked,
14 because it was (I felt) a program that was not particularly complicated
15 or large, but at the same time conveyed several useful,
16 _practical_ concepts that don't show up in Hello World or Project Euler
17 examples.
18
19 The example was a simple MadLibs program. MadLibs is a simple game in
20 which one is presented first with a series of blanks accompanied by
21 requests for particular classes of words—sometimes parts of speech
22 such as nouns or verbs, sometimes more specific descriptors such as
23 food items or animals—and then those are stitched into a sentence,
24 where they produce humorously nonsensical phrases.[^2]
25
26 The program in question required that the MadLib be chosen randomly
27 from a list of possible ones, and that the blanks be requested in
28 a random order which did not necessarily correspond to their ordering
29 in the template phrase.
30
31 A program to implement this would necessarily involve several tasks
32 in the relevant language: input _and_ output from a user, randomness,
33 some kind of data structure for keeping the intermediate values, and
34 possibly some kind of string processing. If one adds a small extra
35 stipulation—that the MadLibs templates must be read from a file—then
36 it also involves file IO, making it a good whirlwind tour of a
37 language's features in practice.
38
39 What I'd like to do is use this MadLibs program as a way of exploring
40 other programming languages, and in particular, niche, emerging, or
41 forgotten languages which people might not be widely familiar with.
42
43 ## The MadLibs Program
44
45 The rough steps which will be performed in most versions of the
46 program are as follows:
47
48 1. Open a file named `template.mad`. This file contains one or more
49 lines that have sentences with certain parts inside matching
50 curly braces, e.g.
51
52 > This {noun} will {verb}!
53
54 After this file is read in, one of the lines is selected at random
55 to be the template used.
56
57 2. Split that template into two sequences: the literal text segments
58 and the part-of-speech segments, as in
59
60 > ["This", "will", "!"]
61 > [ "noun", "verb" ]
62
63 3. Print out requests for the part-of-speech segments, read in
64 lines from the user, and fill them into the template. Do this in
65 random order but still filling in the correct gap.
66
67 > Give me a verb: jump
68 > Give me a noun: dog
69
70 4. Print the resulting sentence
71
72 > This dog will jump!
73
74 [^2]: I played this a lot as a child, in which most of the words
75 chosen were scatological or otherwise puerile. Which is to say:
76 most of the blanks were filled in with some variation on "poop".
(New empty file)
1 For the first MadLibs examples, let's try writing some Lisps. People
2 soetimes talk about 'Lisp' as though it were some specific language,
3 but Lisp is a family of related languages with some commonalities.
4 Broadly speaking, Lisps are dynamically typed, have functional
5 features like closures but aren't necessarily pure, and use a syntax
6 based on S-expressions. These aren't always the case—Shen is a
7 statically typed Lisp, Common Lisp has functional features but is
8 often used imperatively, and Dylan is a Lisp that uses a more
9 traditional Algol-like syntax—but they are broadly applicable.
10
11 The S-expression syntax is often a sticking point for people first
12 learning Lisp, but it's also one of the powerful features of
13 Lisp. In a typical Algol-descended language like (say) C, a
14 compiler will start by taking your textual source and parsing
15 it into a kind of tree. An expression like `f(3 + x * 2)` has
16 to be transformed int oa hierarchical representation that the
17 computer can understand, applying order-of-operations so
18 that it's clear how to group the relevant operations.
19
20 ~~~~
21 |
22 +--+---+
23 | |
24 | +--+--+
25 | | |
26 | | +-+-+
27 | | | |
28 f ( 3 + x * 2 )
29 ~~~~
30
31 In typical Lisp syntax, this tree is written more-or-less
32 explicitly, using parentheses to indicate the relevant grouping,
33 as `(f (+ 3 (* x 2)))`. This means that arithmetic expressions
34 can be a bit syntactically noisy, but has the benefit of being
35 very clear and uniform. Even higher-level syntactic constructs
36 use this format: where C would use curly braces or some other
37 syntactic construct, Lisps continue to use parentheses. For example,
38 the C program
39
40 ~~~{.c}
41 int max(int x, int y)
42 {
43 if (x > y) {
44 return x;
45 } else {
46 return y;
47 }
48 }
49 ~~~
50
51 is analogous to the Scheme program
52
53 ~~~~{.scheme}
54 (define (max x y)
55 (if (> x y) x y))
56 ~~~~
57
58 This makes
1 PicoML
2 ======
3
4 PicoML is an ML- and Haskell-inspired language that is designed to be
5 easy to implement, straightforward, consistent, and small.
6
7 ~~~~
8 fact : Int -> Int
9 0 = 1
10 n = n * fact (n - 1)
11
12 main () = putln ("fact 5 = " <> fact 5)
13 ~~~~
14
15 Type declarations are done either with the `data` or the `record` keywords:
16
17 ~~~~
18 data List a =
19 [ Nil
20 , Cons a (List a)
21 ]
22
23 record Stream a =
24 { .head a
25 , .tail (Stream a)
26 }
27 ~~~~
28
29 Types created with `data` are always eager and types created with `record`
30 are always lazy, so you can easily create infinite streams:
31
32 ~~~~
33 streamZip : (a -> b -> c) -> Stream a -> Stream b -> Stream c
34 streamZip f s1 s2 =
35 { .head = f s1.head s2.head
36 , .tail = streamZip f s1.tail s2.tail
37 }
38
39 fibs : Stream Int =
40 { .head = 1
41 , .tail = streamZip _+_ fibs fibs.tail
42 }
43 ~~~~
44
45 Recursive bindings are allowed in a type-directed way, so a binding whose
46 type is a function or a record will allow recursive bindings, but a binding
47 whose type is data will not allow a recursive binding.
48
49 ~~~~
50 listZip : (a -> b -> c) -> List a -> List b -> List c
51 f (Cons x xs) (Cons y ys) = Cons (f x y) (listZip f xs ys)
52 _ _ _ = Nil
53
54 tail : List a -> List a
55 tail (Cons x xs) = xs
56 tail Nil = error "tail of empty list"
57
58 -- this definition will fail, because one cannot recursively refer to
59 -- value bindings
60 badFibs : List Int
61 = Cons 1 (zip _+_ badFibs (tail badFibs))
62 ~~~~
63
64 The language is not pure, but all bindings are immutable, so reference cells
65 are provided in the stdlib:
66
67 ~~~~
68 impFact : Int -> Int
69 n = let acc = ref 0
70 ; let num = ref n
71 ; while { !num > 0 }
72 { acc := !acc * !num
73 ; num := !num - 1
74 }
75 ; !acc
76 ~~~~
77
78 This also demonstrates the other usage of curly braces: they are sugar for
79 an anonymous function that takes a `()` argument, i.e. the above `impFact`
80 definition is equivalent to
81
82 ~~~~
83 impFact : Int -> Int
84 n = let acc = ref 0
85 ; let num = ref n
86 ; while (() -> !num > 0) (() ->
87 ( () ->
88 acc := !acc * !num
89 ; num := !num - 1
90 )
91 ; !acc
92 ~~~~
93
94 This means that `while` is a function included in the stdlib, as well:
95
96 ~~~~
97 while : Thunk Bool -> Thunk () -> ()
98 cond body = if | cond () -> (body () ; while cond body)
99 | else -> ()
100 ~~~~
101
102 Finally, PicoML has a mechanism for supplying values implicitly. The
103 `implicit` keyword can be used on value bindings and on function
104 arguments. The language will then infer, based on the type, whether or
105 not the user supplied the argument, or whether it needs to reach for
106 the implicit argument:
107
108 ~~~
109 implicit n : Int = 5
110
111 shout : implicit Int -> ()
112 x = println (show x)
113
114 main =
115 { shout 8 -- prints 8
116 ; shout -- prints 5
117 }
118 ~~~
119
120 If there are _multiple_ implicit values in scope whose types match,
121 then PicoML will raise a compile-time error:
122
123 ~~~~
124 implicit n : Int = 5
125 implicit m : Int = 6
126
127 main = { shout } -- will not compile, because of the ambiguity
128 -- as to which implicit to choose
129 ~~~~
130
131 This, combined with manual dictionary-passing, is how typeclass-like
132 functionality is implemented in PicoML
133
134 ~~~~
135 record Monoid m =
136 { .mempty m
137 , .mappend (m -> m -> m)
138 }
139
140 implicit additiveMonoid : Monoid Int =
141 { .mempty = 0
142 , .mappend = _+_
143 }
144
145 multiplicativeMonoid : Monoid Int =
146 { .mempty = 1
147 , .mappend = _*_
148 }
149
150 mconcat : implicit (Monoid a) -> List a -> a
151 m (Cons x xs) = m.mappend x (mconcat m xs)
152 m Nil = m.mempty
153
154 main =
155 { print (mconcat [2,3,4]) -- prints 9
156 ; print (mconcat multiplicativeMonoid [2,3,4]) -- prints 24
157 }
158 ~~~~