summaryrefslogtreecommitdiff
path: root/notes/cons.md
blob: 29bb2d6f6b3a5366a8f212647ee415b4c0afcd9a (plain)
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# Stop the "cons" madness!

Lists are neat, but they aren't the best representation for sequences
of fixed length.  An array/vector is a better choice for this.

R6RS already uses vectors in some places where a traditional lisper
may have expected to see lists.  Namely, in the procedural layer of
record types, where the fields of a record type are represented by
vectors.  Another example is `hashtable-keys` and `hashtable-entries`
which both return vectors.  There may be more places in Scheme where
this makes sense.

In the following, we discuss a better handling of rest-argument lists
and a change to `apply`.  In short, rest arguments are not actually
lists anymore, but rather a special kind of identifier that refers to
multiple values.  And `apply`, which becomes a special form, expects
its last argument not to be a list but rather an expression that may
evaluate to multiple values.


## Better handling of rest args

Initially, I was thinking of using either immutable vectors, or
immutable lists transparently backed by arrays, to represent rest
arguments.  But the following paper offers a very compelling
alternative:

[A New Approach to Procedures with Variable Arity](https://legacy.cs.indiana.edu/~dyb/pubs/LaSC-3-3-pp229-244.pdf)

Let's first summarize the paper, and then see how we can adapt its
ideas for Zisp.

Long story short, rest argumenst are received through a parameter list
such as `(arg1 ... argn & rest)` and the identifier `rest` is special
in that it can only be passed on to another procedure using the same
syntax.  For example, to explicitly put the rest args into a list and
map over them:

```scheme

(define (map* proc & args)
  (map proc (list & args)))

(map* square 1 2 3) ;=> (1 4 9)

```

Recursive functions that directly consume an arbitrary number of args,
without needing to allocate any data structure, can be implemented by
combining this feature with what is today known as `case-lambda`:

```scheme

(define combine*
  (case-lambda
    ((x) x)
    ((x y & rest) (combine* (combine x y) & rest))

```

Though the paper proposes the use of `&` so as to differentiate it
from the regular rest-argument mechanism of Scheme, I intend to make
Zisp use only this mechanism, so we can use the dot notation for it.
Rewriting the above examples in this style gives us:

```scheme

(define (map* proc . args)
  (map proc (list . args)))

(define combine*
  (case-lambda
    ((x) x)
    ((x y . rest) (combine* (combine x y) . rest))))

```

I find this very pleasing on the eyes, and a very elegant way to use
improper lists in evaluation context, which isn't allowed in Scheme.


## More ergonomic multiple-values

The paper linked above proposes to reuse the rest args syntax for an
elegant solution to consuming multiple values:

```scheme

(proc x y & <expr>)

```

In the above, `<expr>` may evaluate to multiple values, and the values
will be passed to `proc` as additional arguments.

Essentially, this means that the special rest-arg identifier is itself
a representation of multiple values, or in other words, evaluating it
results in multiple values even though it's just an identifier!

Demonstration, using Zisp notation for multiple-value rest args:

```scheme

(define (foobar . rest)
  (let-values (((x y z) rest))
    ;; This is a meaningless example for demonstration, since we
    ;; could have just made the function accept three parameters.
    ;; The `let-values` here is the one from SRFI 11.
    ))

```

The paper also proposes terminating lambda bodies with `& <expr>` to
return multiple values, but this is obsolete as of R5RS, which allows
the final expression in a body to evaluate to multiple values anyway.

However, the use of & to pass multiple values as arguments is very
convenient and much cleaner than R5RS's clumsy `call-with-values`:

```scheme

;; Returns {bool, obj} where bool indicates success/failure and obj
;; is meaningless if bool is false; allows differentiating between
;; the case where #f is found as the value vs. nothing being found.
(define (lookup alist key)
  (if (null? alist)
      (values #f #f)
      (if (eqv? key (caar alist))
          (values #t (cdar alist))
          (lookup (cdr alist) key))))

(define (display-if-found found? obj)
  (when found? (display obj)))

;; Incredibly ugly `call-with-values`:
(call-with-values
  (lambda () (lookup '((x . y)) 'x))
  display-if-found)

;; (Up until here is valid R5RS code, by the way.)

;; So much cleaner:
(display-if-found & (lookup '((x . y)) 'x))  ;; displays x

```

Unfortunately, we can't reuse the improper list syntax in the last
example, since the following s-expressions are equivalent:

```scheme

(foo . (bar baz))
(foo bar baz)

```

In Zisp, this will be solved by making `apply` a special-form where
the last operand is expected to evaluate to multiple values rather
than a list:

```scheme

;; (apply <proc-expr> <argn-expr> ... <restargs-expr>)

(apply display-if-found (lookup '((x . y)) 'x))

```

Note that this means the forms `(apply foo rest)` and `(foo . rest)`
are equivalent if `rest` is an identifier and not a pair/list, while
`(apply foo (x ...))` is of course different from `(foo x ...)`.

I find this all incredibly pleasing.  Lists never had any business in
representing arguments in the first place; it should always have been
multiple values!

(The phrase "argument list" is probably going to stick around forever
though, even if it's technically wrong in Zisp.)