-
Notifications
You must be signed in to change notification settings - Fork 0
/
bel.txt
4540 lines (3238 loc) · 137 KB
/
bel.txt
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
The Bel Language 12 Oct 2019
In 1960 John McCarthy described a new type of programming language
called Lisp. I say "new type" because Lisp represented not just a new
language but a new way of describing languages. He defined Lisp by
starting with a small set of operators, akin to axioms, and then
using them to write an interpreter for the language in itself.
His goal was not to define a programming language in our sense of
the word: a language used to tell computers what to do. The Lisp
in his 1960 paper was meant to be a formal model of computation,
like a Turing Machine. McCarthy didn't realize it could be used on
computers till his graduate student Steve Russell suggested it.
The Lisp in the 1960 paper was missing features you'd need in a
programming language. There were no numbers, for example, or errors,
or I/O. So when people used it as the basis for languages used to
program computers, such things had to be added. And when they were,
the axiomatic approach was dropped.
So the development of Lisp happened in two parts (though they seem
to have been interleaved somewhat): a formal phase, represented by
the 1960 paper, and an implementation phase, in which this language
was adapted and extended to run on computers. Most of the work, as
measured by features, took place in the implementation phase. The
Lisp in the 1960 paper, translated into Common Lisp, is only 53 lines
of code. It does only as much as it has to in order to interpret
expressions. Everything else got added in the implementation phase.
My hypothesis is that, though an accident of history, it was a good
thing for Lisp that its development happened in two phases-- that
the initial exercise of defining the language by writing an
interpreter for it in itself is responsible for a lot of Lisp's best
qualities. And if so, why not do more of it?
Bel is an attempt to answer the question: what happens if, instead of
switching from the formal to the implementation phase as soon as
possible, you try to delay that switch for as long as possible? If
you keep using the axiomatic approach till you have something close
to a complete programming language, what axioms do you need, and what
does the resulting language look like?
I want to be clear about what Bel is and isn't. Although it has a lot
more features than McCarthy's 1960 Lisp, it's still only the product
of the formal phase. This is not a language you can use to program
computers, just as the Lisp in the 1960 paper wasn't. Mainly because,
like McCarthy's Lisp, it is not at all concerned with efficiency.
When I define append in Bel, I'm saying what append means, not trying
to provide an efficient implementation of it.
Why do this? Why prolong the formal phase? One answer is that it's
an interesting exercise in itself to see where the axiomatic approach
leads. If computers were as powerful as we wanted, what would
languages look like?
But I also believe that it will be possible to write efficient
implementations based on Bel, by adding restrictions. If you want a
language with expressive power, clarity, and efficiency, it may work
better to start with expressive power and clarity, and then add
restrictions, than to approach from another direction.
So if you'd like to try writing an implementation based on Bel,
please do. I'll be one of your first users.
I've ended up reproducing a number of things in previous dialects.
Either their designers got it right, or I'm too influenced by
dialects I've used to see the right answer; time will tell. I've also
tried to avoid gratuitous departures from existing Lisp conventions.
Which means if you see a departure from existing conventions, there
is probably a reason for it.
Data
Bel has four fundamental data types: symbols, pairs, characters, and
streams.
Symbols are words:
foo
The names of symbols are case-sensitive, so foo and Foo are distinct
symbols.
Pairs are pairs of any two things, and are represented thus:
(foo . bar)
That's a pair of two symbols, foo and bar, but the two halves of a
pair can be anything, including pairs:
(foo . (bar . baz))
That's a pair of the symbol foo, and the pair (bar . baz).
A character is represented by prepending a backslash to its name. So
the letter a is represented as
\a
Characters that aren't letters may have longer names. For example the
bell character, after which Bel is named, is
\bel
There is no way of representing a stream. If Bel has to display a
stream, it prints something that will cause an error if it's read
back in.
Anything that's not a pair is called an atom. So symbols, characters,
and streams are atoms.
Instances of the four fundamental types are called objects.
Lists
We can use pairs to build lots of different data structures, but the
most fundamental way they're used is to make lists, as follows:
1. The symbol nil represents the empty list.
2. If y is a list, then the pair (x . y) is a list of x followed by
the elements of y.
Here's a list made from a pair:
(a . nil)
According to rule 2, this is a list of the symbol a followed by the
elements of nil, of which according to rule 1 there are none. So it
is a list of one element, the symbol a.
By nesting such pairs we can create lists of any length. Here is a
list of two elements, the symbols a and b:
(a . (b . nil))
And here is a list of a, b, and c:
(a . (b . (c . nil)))
This would be an awkward way to express lists, but there is an
abbreviated notation that's more convenient:
1. The symbol nil can also be represented as ().
2. When the second half of a pair is a list, you can omit the dot
before it and the parentheses around it. So (a . (b ...)) can
be written as (a b ...).
By repeated application of these two rules we can transform
(a . (b . (c . nil)))
into
(a b c)
In other words, a list can be expressed as its elements within
parentheses. You wouldn't use dot notation for a list like (a b c)
unless there was some special reason to.
Because any object can be part of a pair, the elements of lists can
themselves be lists. All these are lists too:
(a (b) c)
((a b c))
(nil)
Pairs like these, where if you keep looking at the second half you
eventually hit a nil, are called proper lists. This is a proper
list:
(a b c)
and this is not:
(a b . c)
The empty list is also a proper list.
A pair that's not a proper list is called a dotted list (because
you need to use dot notation to represent it).
A proper list of characters is called a string, and can also be
represented as those characters within double-quotes. So the list
(\h \e \l \l \o)
can also be represented as
"hello"
and will when possible be displayed that way.
Truth
The symbol nil represents falsity as well as the empty list. The
symbol t is the default representation for truth, but any object
other than nil also counts as true.
It may seem strange to use the same value to represent both falsity
and the empty list, but in practice it works well. Lisp functions
often return sets of answers, and the empty set of answers is
falsity.
Functions
Bel programs consist mostly of functions. Functions take zero or more
objects as arguments, perhaps do something (e.g. print a message),
and return one object.
Functions are represented using lists. For example, here is a
function that takes one argument, and returns that plus 1.
(lit clo nil (x) (+ x 1))
The first element, lit, says that this is a literal object, not to be
evaluated.
The second, clo, says what kind of literal it is: a closure.
The third is the local enviroment, a list of variables that already
have values from having been parameters of functions. This example
has an empty environment.
The fourth, (x), is the function's parameters. When the function is
called, the value of x will be whatever it was called with.
The fifth and last element, (+ x 1), defines the value that the
function returns.
You would not ordinarily express a function using its literal
representation. Usually you'd say
(fn (x) (+ x 1))
which yields the function above.
Evaluation
The execution of a Bel program consists of the evaluation of
expressions. All Bel objects are expressions, so the word "expression"
is merely a statement of intention: it means an object that you
expect to be evaluated.
When an expression is evaluated, there are three possible outcomes:
1. It can return a value: (+ 1 2) returns 3.
2. It can cause an error: (/ 1 0) will.
3. It can fail to terminate: (while t) will.
Some expressions also do things in the process of being evaluated.
For example,
(prn 1)
will return 1, but before doing so will print it.
Some atoms evaluate to themselves. All characters and streams do,
along with the symbols nil, t, o, and apply. All other symbols
are variable names, and either evaluate to some value, or cause an
error if they don't have a value.
A proper list whose first element evaluates to a function is called
a function call. For example, the expression
(+ x 1)
is a function call, because the value of + is a function. The value
of a function call is the object that the function returns.
Function calls are evaluated left to right. For example, when
(+ 8 5)
is evaluated,
1. First + is evaluated, returning a function that returns the sum
of its arguments.
2. Then 8 is evaluated, returning itself.
3. Then 5 is evaluated, also returning itself.
4. Finally, the two numbers are passed to the function, which returns
13.
If we want to show what expressions evaluate to, it's conventional to
show them being evaluated in a repl:
> (+ 8 5)
13
Expressions can be nested. The rule of evaluating left to right means
that nested function calls are evaluated depth-first. For example,
when
(+ (- 5 2) 7)
is evaluated, the subexpressions that get evaluated are, in order,
+, (- 5 2), -, 5, 2, 7.
Not all expressions are evaluated left to right, however. There is a
small set of symbols called special forms, and an expression whose
first element is a special form is evaluated according to rules
defined for that special form.
For example, if is a special form, and when an expression of the form
(if test then else)
is evaluated, only one of the last two elements will be evaluated,
depending on whether the second element, test, returns true or not.
Things meant to be used as the first element of an expression are
called operators. So functions and special forms are operators. But
like the term "expression," this is just a statement of intention.
You can put anything first in an expression so long as you specify
what happens when you do.
Bindings and Environments
There are three ways a variable can have a value. It can have a value
globally, as for example + does, meaning that by default it has this
value everywhere. Such a variable is said to be globally bound, and
the set of global bindings is called the global environment.
Another way a variable can have a value is by being a parameter in a
function. When the function
(fn (x) (+ x 1))
is called, the variable x will, within it, have as its value whatever
argument the function was called with. That's called a lexical
binding, and the current set of lexical bindings is the lexical
environment.
Finally, variables can have dynamic bindings, which are visible
everywhere, like global bindings, but temporary: they persist only
during the evaluation of whatever expression created them.
Dynamic bindings take precendence over lexical bindings, which take
precedence over global ones.
If you do an assignment to a variable that has one of the three kinds
of bindings, you'll modify whichever binding is currently visible. If
you do an assignment to a variable that's not bound, you'll create a
global binding for it.
Errors
Errors are signalled by calling err with one argument describing the
error. Bel doesn't specify a global binding for err; this is
something for a programming environment built on top of Bel to do.
But some error-catching code in the Bel source does dynamically bind
err.
Axioms
Like McCarthy's Lisp, Bel is defined starting with a set of operators
that we have to assume already exist. Then more are defined in terms
of these, till finally we can define a function that is a Bel
interpreter, meaning a function that takes a Bel expression as an
argument and evaluates it.
There are two main types of axioms: primitives and special forms.
There are also a few variables that come predefined.
In the following sections, the descriptions of primitives are their
definitions. The descriptions of special forms, however, are merely
summaries of their behavior; special forms are defined by the code
that implements them in the Bel source.
Variables and Constants
1. t nil o apply
Evaluate to themselves.
2. chars
A list of all characters. Its elements are of the form (c . b), where
c is a character and b is its binary representation in the form of a
string of \1 and \0 characters. Bel doesn't specify which characters
are in chars, but obviously they should include at least those in the
Bel source.
3. globe scope
The current global and lexical environments, represented as lists of
(var . val) pairs of variables and their values.
4. ins outs
The default input and output streams. Initially nil, which represents
the initial input and output streams. Bel doesn't specify what those
are, but if you started Bel at a prompt you'd expect them to be the
terminal.
Quote
The quote operator is a special form, but it has to be described
first because so many code examples use it.
It returns its argument without evaluating it. Its purpose is to
prevent evaluation.
> (quote a)
a
Prepending ' to an expression is equivalent to wrapping a quote
around it.
> 'a
a
Why do you need to prevent evaluation? To distinguish between code
and data. If you want to talk about the symbol a, you have to quote
it. Otherwise it will be treated as a variable, and you'll get its
value. E.g. if a has been set to 10:
> a
10
> 'a
a
Because the symbols nil, t, o, and apply evaluate to themselves, you
don't have to quote them. Ditto for strings.
Primitives
Primitives can be called like functions, but are assumed to exist,
rather than defined in the Bel source. As with function calls, the
arguments in calls to primitives are all evaluated, left to right.
Missing arguments default to nil. Extra arguments cause an error to
be signalled.
1. (id x y)
Returns true iff x and y are identical.
> (id 'a 'a)
t
> (id 'a 'b)
nil
Identity is stricter than equality. While there is only one of each
symbol and character, there can be any number of different pairs with
the same elements. So two pairs can look the same without being
identical:
> (id '(a b) '(a b))
nil
Because id is so strict, it's not the function you'd usually use to
test for equality. Usually you'd use =.
2. (join x y)
Returns a new pair whose first half is x and second half is y.
> (join 'a 'b)
(a . b)
> (join 'a)
(a)
A pair returned by join will not be id to any existing pair.
> (id (join 'a 'b) (join 'a 'b))
nil
3. (car x)
Returns the first half of a pair:
> (car '(a . b))
a
> (car '(a b))
a
The car of nil is nil,
> (car nil)
nil
but calling car on any atom other than nil will cause an error.
The name "car" is McCarthy's. It's a reference to the architecture of
the first computer Lisp ran on. But though the name is a historical
accident, it works so well in practice that there's no reason to
change it.
4. (cdr x)
Returns the second half of a pair:
> (cdr '(a . b))
b
> (cdr '(a b))
(b)
As with car, calling it on nil yields nil, calling it on any other
atom causes an error, and the name is McCarthy's.
When operating on pairs used to represent lists, car and cdr get you
the first element and the rest of the list respectively.
5. (type x)
Returns either symbol, pair, char, or stream depending on the type
of x.
> (type 'a)
symbol
> (type '(a))
pair
> (type \a)
char
6. (xar x y)
Replaces the car of x with y, returning y. Signals an error if x is
not a pair.
If we assume that the value of x is (a . b), then
> x
(a . b)
> (xar x 'c)
c
> x
(c . b)
7. (xdr x y)
Like xar, except that it replaces the cdr of x.
> x
(c . b)
> (xdr x 'd)
d
> x
(c . d)
8. (sym x)
Returns the symbol whose name is the elements of x. Signals an error
if x is not a string.
> (sym "foo")
foo
9. (nom x)
Returns a fresh list of the characters in the name of x. Signals an
error if x is not a symbol.
> (nom 'foo)
"foo"
10. (wrb x y)
Writes the bit x (represented by either \1 or \0) to the stream y.
Returns x. Signals an error if it can't or if x is not \1 or \0. If y
is nil, writes to the initial output stream.
11. (rdb x)
Tries to read a bit from the stream x. Returns \1 or \0 if it finds
one, nil if no bit is currently available, or eof if no more will be
available. Signals an error if it can't. If x is nil, reads from the
initial input stream.
12. (ops x y)
Returns a stream that writes to or reads from the place whose name is
the string x, depending on whether y is out or in respectively.
Signals an error if it can't, or if y is not out or in.
13. (cls x)
Closes the stream x. Signals an error if it can't.
14. (stat x)
Returns either closed, in, or out depending on whether the stream x
is closed, or reading from or writing to something respectively.
Signals an error if it can't.
15. (coin)
Returns either t or nil randomly.
16. (sys x)
Sends x as a command to the operating system.
Special Forms
Expressions beginning with special forms are not always evaluated in
the usual left-to-right way.
1. (quote x)
Described above.
2. (lit ...)
Returns the whole lit expression without evaluating it. A lit is like
a persistent quote; evaluation strips the quote off a quote
expression, but leaves a lit expression intact.
> (quote a)
a
> (lit a)
(lit a)
The name stands for literal, and it can take any number of arguments.
This is how you make things that evaluate to themselves, the way
characters or nil do. Functions are lits, for example, as are
numbers.
The value of a primitive p is (lit prim p).
> car
(lit prim car)
3. (if ...)
An if expression with an odd number of arguments
(if a1 a2 a3 a4 ... an)
is equivalent to
if a1 then a2 else if a3 then a4 ... else an
I.e. the odd numbered arguments are evaluated in order till we either
reach the last, or one returns true. In the former case, its value
is returned as the value of the if expression. In the latter, the
succeeding argument is evaluated and its value returned.
An if expression with an even number of arguments
(if a1 a2 ... an)
is equivalent to
(if a1 a2 ... an nil)
Falsity is represented by nil, and truth by any other value. Tests
generally return the symbol t when they can't return anything more
useful.
As a rule I've tried to make axioms as weak as possible. But while
a 3-argument if would have sufficed, an n-argument version didn't
require significantly more code, so it seemed gratuitously fussy to
insist on 3 arguments.
4. (apply f ...)
An expression of the form
(apply f x y ... z)
is equivalent to
(f 'a 'b ... 'c1 ... 'cn)
where a is the value of x, b the value of y, and the ci the elements
of the value of z.
> (join 'a 'b)
(a . b)
> (apply join '(a b))
(a . b)
> (apply join 'a '(b))
(a . b)
The last argument to apply can be a dotted list if it matches the
parameters of the first.
5. (where x)
Evaluates x. If its value comes from a pair, returns a list of that
pair and either a or d depending on whether the value is stored in
the car or cdr. Signals an error if the value of x doesn't come from
a pair.
For example, if x is (a b c),
> (where (cdr x))
((a b c) d)
6. (dyn v x y)
Evaluates x, then causes y to be evaluated with the variable v
dynamically bound to the value of x.
For example, if x is a,
> x
a
> (dyn x 'z (join x 'b))
(z . b)
> x
a
7. (after x y)
Evaluates both its arguments in order. The second will be evaluated
even if the evaluation of the first is interrupted (e.g. by an
error).
8. (ccc f)
Evaluates f and calls its value on the current continuation. The
continuation, if called with one argument, will return it as the
value of the ccc expression (even if you are no longer in the ccc
expression or in code called by it).
9. (thread x)
Starts a new thread in which x will be evaluated. Global bindings are
shared between threads, but not dynamic ones.
Reading the Source
Starting with the foregoing 25 operators, we're going to define more,
till we can define a Bel interpreter. Then we'll continue, defining
numbers, I/O, and several other things one needs in programs.
These definition are in the Bel source, which is meant to be read in
parallel with this guide.
In the Bel source, when you see an expression of the form
(set v1 e1 ... vn en)
it means each vi is globally bound to the value of ei.
In the source I try not to use things before I've defined them, but
I've made a handful of exceptions to make the code easier to read.
When you see
(def n p e)
treat it as an abbreviation for
(set n (lit clo nil p e))
and when you see
(mac n p e)
treat it as an abbreviation for
(set n (lit mac (lit clo nil p e)))
The actual def and mac operators are more powerful, but this is as
much as we need to start with.
Treat an expression in square brackets, e.g.
[f _ x]
as an abbreviation for
(fn (_) (f _ x))
In Bel, underscore is an ordinary character and _ is thus an ordinary
variable.
Finally, treat an expression with a prepended backquote (`) as a
quoted list, but with "holes," marked by commas, where evaluation
is turned back on again.
> (set x 'a)
a
> `(x ,x y)
(x a y)
> `(x ,x y ,(+ 1 2))
(x a y 3)
You can also use ,@ to get a value spliced into the surrounding
list:
> (set y '(c d))
(c d)
> `(a b ,@y e f)
(a b c d e f)
Now let's look at the source. The first expression defines a function
no that takes one argument, x, and returns the result of using id to
compare it to nil. So no returns t if its argument is nil, and nil
otherwise.
> (no nil)
t
> (no 'a)
nil
Since nil represents both falsity and the empty list, no is both
logical negation and the test for the empty list.
The second function, atom, returns true iff its argument is not a
pair.
> (atom \a)
t
> (atom nil)
t
> (atom 'a)
t
> (atom '(a))
nil
Next come a pair of similar functions, all and some. The former
returns t iff its first argument returns true of all the elements of
its second,
> (all atom '(a b))
t
> (all atom nil)
t
> (all atom '(a (b c) d))
nil
and the latter returns true iff its first argument returns true of
any element of its second. However, when some returns true, it
doesn't simply return t. It returns the remainder of the list
starting from the point where f was true of the first element.
> (some atom '((a b) (c d)))
nil
> (some atom '((a b) c (d e)))
(c (d e))
Logically, any value except nil counts as truth, so why not return
the most informative result you can?
In all and some we see the first use of if. Translated into English,
the definition of all might be:
If xs is empty, then return t.
Otherwise if f returns true of the first element, return the result
of calling all on f and the remaining elements.
Otherwise (in which case xs has at least one element of which f
returns false), return nil.
This technique of doing something to the car of a list and then
perhaps continuing down the cdr is very common.
Something else is new in all and some: these are the first functions
in the Bel source that you could cause an error by calling.
> (all atom 'a)
Error: can't call car on a non-nil atom.
I made up that error message; Bel doesn't specify more about errors
in primitives than when they occur, and doesn't specify anything
about repls. But some error will be signalled if you call all with a
non-nil atom as the second argument, because in the second test
within the if
(f (car xs))
car is called on it, and it's an error to call car on anything except
a pair or nil.
One other thing to note about these definitions, now that we're
getting to more complex ones: these functions are not defined the
way they would be in idiomatic Bel. For example, if all didn't
already exist in Bel you could define it as simply
(def all (f xs)
(~some ~f xs))
But since we haven't defined functional composition yet, I didn't use
it.
The next function, reduce, is for combining the elements of its
second argument using nested calls to its first. For example
(reduce f '(a b c d))
is equivalent to
(f 'a (f 'b (f 'c 'd)))
If xs has only one element, reduce returns it, and if it's empty,
reduce returns nil; since (cdr nil) is nil, we can check both these
possibilities with (no (cdr xs)). Otherwise it calls f on the first
element and reduce of f and the remaining elements.
> (reduce join '(a b c))
(a b . c)
This is not the only way to reduce a list. Later we'll define two
more, foldl and foldr.
The definition of reduce shows another way of indenting ifs.
Indentation isn't significant in Bel and only matters insofar as
it helps humans read your code, but I've found three ways of
indenting ifs that work well. If an if has more than two tests and
the arguments are sufficiently short, it works well to say
(if test1 then1
test2 then2
else)
We saw this in all and some. But if you only have one test, or some
arguments are too long to fit two on one line, then it works better
to say
(if test1
then1
test2
then2
else)
or if an if is long,
(if test1
then1
test2
then2
test3
then3
else)
The next function, cons, has the name that join had in McCarthy's
Lisp. It's the function you use to put things on the front of a list.
> (cons 'a '(b c))
(a b c)
If you only want to put one thing on the front of a list, you could
use join.
> (join 'a '(b c))
(a b c)
With cons, however, you can supply more than one thing to put on the
front:
> (cons 'a 'b 'c '(d e f))
(a b c d e f)
Since cons is a generalization of join, it's rare to see join in
programs.
We see something new in the definition of cons: it has a single