creating a Lisp variant in seven forms
object EndlessLoop extends Baysick {
def main(args:Array[String]) = {
10 PRINT "Haikeeba!"
20 GOTO 10
30 END
RUN
}
}
object Lunar extends Baysick {
def main(args:Array[String]) = {
20 LET ('dist := 100)
30 LET ('v := 1)
40 LET ('fuel := 1000)
50 LET ('mass := 1000)
60 PRINT "You are a in control of a lunar lander, drifting towards the moon."
80 PRINT "Each turn you must decide how much fuel to burn."
90 PRINT "To accelerate enter a positive number, to decelerate a negative"
100 PRINT "Dist " % 'dist % "km, " % "Vel " % 'v % "km/s, " % "Fuel " % 'fuel
110 INPUT 'burn
120 IF ABS('burn) <= 'fuel THEN 150
130 PRINT "You don't have that much fuel"
140 GOTO 100
150 LET ('v := 'v + 'burn * 10 / ('fuel + 'mass))
160 LET ('fuel := 'fuel - ABS('burn))
170 LET ('dist := 'dist - 'v)
180 IF 'dist > 0 THEN 100
190 PRINT "You have hit the surface"
200 IF 'v < 3 THEN 240
210 PRINT "Hit surface too fast (" % 'v % ")km/s"
220 PRINT "You Crashed!"
230 GOTO 250
240 PRINT "Well done"
250 END
RUN }}
car
and cdr
)cond
)Had
label
and lambda
, dynamic
scoping [3], lists (kinda)
Didn't Have
closures, macros, numbers
(label and
(lambda (and_x and_y)
(cond (and_x
(cond (and_y t)
(t nil)))
(t nil))))
(and t nil)
;=> nil
(and t t)
;=> t
(label list
(lambda (x y)
(cons x (cons y (quote ())))))
(def append
(lambda (append_x append_y)
(cond ((null append_x) append_y)
(t (cons (car append_x)
(append (cdr append_x) append_y))))))
(append (list 1 2) (list 3 4))
;=> (1 2 3 4)
You can see where this is going...
(def eval (lambda (expr binds)
(cond
((atom expr) (assoc expr binds))
((atom (car expr))
(cond
((eq (car expr) (quote quote)) (cadr expr))
((eq (car expr) (quote atom)) (atom (eval (cadr expr) binds)))
((eq (car expr) (quote eq)) (eq (eval (cadr expr) binds)
(eval (caddr expr) binds)))
((eq (car expr) (quote car)) (car (eval (cadr expr) binds)))
((eq (car expr) (quote cdr)) (cdr (eval (cadr expr) binds)))
((eq (car expr) (quote cons)) (cons (eval (cadr expr) binds)
(eval (caddr expr) binds)))
((eq (car expr) (quote cond)) (eval-cond (cdr expr) binds))
(t (eval (cons (assoc (car expr) binds)
(cdr expr))
binds))))
((eq (caar expr) (quote def))
(eval (cons (caddar expr) (cdr expr))
(cons (list (cadar expr) (car expr)) binds)))
((eq (caar expr) (quote lambda))
(eval (caddar expr)
(append (pair (cadar expr) (eval-args (cdr expr) binds))
binds)))
(t (assoc expr binds)))))
note: not all code shown
1. fn
2. def
3. apply
(def CAR (fn [[h & _]] h))
(def CDR (fn [[_ & t]] t))
(def LST (fn [& args] args))
(def KONS (fn [h t] (apply LST h t)))
(CAR [1 2 3])
;=> 1
(CDR [1 2 3])
;=> (2 3)
(LST 1 2 3 4)
;=> (1 2 3 4)
(KONS 1 [2 3 4 5])
;=> (1 2 3 4 5)
4. if
5. =
nil
-y thing instead(def NIL ((fn [x y] (if (= x y) x))
= (= = =)))
NIL
;=> nil
CONS
(take 1)
(def CONS
(fn [h t]
(fn ([] h)
([_] t))))
(def x (CONS CAR (CONS CDR NIL)))
(x)
;=> #<fojure.core$CAR>
(x NIL)
;=> #<fojure.core$CONS$fn>
((x NIL))
;=> #<fojure.core$CDR>
((x NIL) NIL)
;=> nil
A closure over the head and tail
A good start...
FIRST
and REST
(def FIRST (fn [s] (s)))
(def REST (fn [s] (s NIL)))
(FIRST x)
;=> #<fojure.core$CAR>
(REST x)
;=> #<fojure.core$CONS$fn>
(FIRST (REST x))
;=> #<fojure.core$CDR>
(REST (REST x))
;=> nil
A Poor Man's Object
6. `
shown as unqualified herein
CONS
(def CONS
(fn [h t]
(fn [d]
(if (= d `type)
`CONS
(if (= d `head)
h
t)))))
(def $ (CONS `a (CONS `b NIL)))
;=> #<user$CONS$fn__4 user$CONS$fn__4@61578aab>
($ `type)
;=> CONS
($ `head)
;=> a
(($ `tail) `head)
;=> b
this doesn't count...
(defmethod print-method (class $)
[f w]
(print-method (FIRST f) w)
(print " ") (print-method '. w) (print " ")
(print-method (REST f) w))
$
;=> a . b . nil
What does this look like?
A Poor Man's Closure
`fojure.core/type
to inspect the seq typeCONS
when type is a cons cell`fojure.core/head
to get the first elementFIRST
and REST
(def FIRST
(fn [x]
(if x
(if (= (x `type) `CONS)
(x `head)
(if (x)
((x) `head))))))
(def REST
(fn [x]
(if x
(if (= (x `type) `CONS)
(x `tail)
(if (x)
((x) `tail))))))
(FIRST $)
;=> a
(REST $)
;=> b . nil
(FIRST (REST $))
;=> b
CONS
, FIRST
and REST
!
(def SEQ
(fn [x]
(if x
(if (= (x `type) `CONS)
x
(if (x)
(SEQ (x)))))))
(SEQ $)
;=> a . b . nil
(FIRST (SEQ $))
;=> a
(SEQ (REST (REST $)))
;=> nil
(def APPEND
(fn [l r]
(if (FIRST l)
(CONS (FIRST l)
(APPEND (REST l) r))
r)))
(APPEND (CONS `x nil) (CONS `y (CONS `z NIL)))
;=> x . y . z . nil
(APPEND $ $)
;=> a . b . a . b . nil
But this is not a convenient way to deal with lists
(def LIST
(fn ls
([h] (CONS h nil))
([h t] (CONS h (CONS t nil)))
([h m & [f & r]]
(if (CAR r)
(if (CAR (CDR r))
(APPEND (LIST h m) (apply ls f (CAR r) (CDR r)))
(APPEND (LIST h m) (LIST f (CAR r))))
(CONS h (LIST m f))))))
(LIST `a `b `c `d `e `f)
;=> a . b . c . d . e . f . nil
(SEQ (REST (LIST `a)))
;=> nil
(APPEND (LIST `a `b) (LIST `x `y))
;=> a . b . x . y . nil
Using CAR
, CDR
, and
destructuring as the primordial first
and
rest
(defn ASSOC [k v alist]
(CONS (LIST k v) alist))
(ASSOC `a `foo NIL)
;=> a . foo . nil . nil
(defn GET [k alist]
(if (SEQ alist)
(if (= (FIRST (FIRST alist)) k)
(FIRST (REST (FIRST alist)))
(GET k (REST alist)))))
(GET `a (ASSOC `a `foo NIL))
;=> foo
(defn MAKE-MAP [& kvs]
(if kvs
(ASSOC (CAR kvs)
(CAR (CDR kvs))
(apply MAKE-MAP (CDR (CDR kvs))))))
(GET `b (MAKE-MAP `a 1 `b 2))
;=> 2
(defn ATOM? [x]
(if x
(if (= (x `type) `CONS)
NIL
`TRUTHY)))
(ATOM? (LIST 1 2))
;=> nil
(ATOM? `x)
;=> TRUTHY
(defn EVAL [expr binds]
(if (ATOM? expr)
(GET expr binds)))
(EVAL `a (MAKE-MAP `a 42 `b 2))
;=> 42
(defn SECOND [s]
(FIRST (REST s)))
(defn EVAL [expr binds]
(if (ATOM? expr)
(GET expr binds)
(if (ATOM? (FIRST expr))
(if (= (FIRST expr) `|quote|
(SECOND expr)))))
(EVAL (LIST `|quote| `a) NIL)
;=> a
(EVAL (LIST `|quote| (LIST `a `b)) NIL)
;=> a . b . nil
(defn EVAL [expr binds]
(if (ATOM? expr)
(GET expr binds)
(if (ATOM? (FIRST expr))
(if (= (FIRST expr) `|quote|)
(SECOND expr)
(if (= (FIRST expr) `|atom?|)
(ATOM? (EVAL (SECOND expr) binds)))))))
(EVAL (LIST `|atom?| (LIST `|quote| `a)) NIL)
; i.e. (|atom?| (QUOTE a))
;=> TRUTHY
(EVAL (LIST `|atom?| `a)
(MAKE-MAP `a `foo))
;=> TRUTHY
(EVAL (LIST `|atom?| `a)
(MAKE-MAP `a (LIST `a)))
;=> nil
(def LAZY-SEQ
(fn [f]
(fn
([x]
(if (= x :type)
`LAZY-SEQ))
([] (f)))))
(FIRST ((LAZY-SEQ (fn [] (LIST `a `b `c)))))
;=> a
((LAZY-SEQ (fn [] (LIST `a `b `c))))
; a . b . c . nil
What is the protocol for LAZY-SEQ
?
(defmethod print-method (class (LAZY-SEQ $))
[f, w]
(print-method (FIRST f) w)
(print " ") (print ". <unrealized>"))
(LAZY-SEQ (fn [] (LIST `a `b `c)))
;=> a . <unrealized>
fn
LAZY-SEQ
`fojure.core/type
(def MAP
(fn [f s]
(LAZY-SEQ
(fn []
(if (SEQ s)
(CONS (f (FIRST s))
(MAP f (REST s))))))))
(MAP keyword (LIST `a `b `c))
;=> :a . <unrealized>
(MAP LIST (LIST `a `b))
;=> a . nil . <unrealized>
(FIRST (MAP LIST (LIST `a `b)))
;=> a . nil
defmacro
(let [a 1]
(let [b 2]
(println [a b]))
(println [a b]))
; java.lang.Exception: Unable to resolve symbol: b in this context
Defines a scope for named values
(defmacro LET [bind val & body]
`((fn [~bind]
~@body)
~val))
(LET a 1
(LET b 2
(LIST a b)))
produces...
((fn [a]
((fn [b]
(LIST a b))
2))
1)
;=> 1 . 2 . nil
more or less
(FIRST
(LET x `a
(CONS x nil)))
;=> a
(LET x `x
(LET y `y
(CONS x (CONS y $))))
; x . y . a . b . nil
apply
...defmacro
gives us that for free
(defmacro APPLY [f args]
`(~f ~@args))
(APPLY + [1 2 3 4])
;=> 10
(PRN (APPLY LIST '[a b c d e]))
; a b c d e
def
fn
apply
=
if
`
defmacro
defmacro
why not?
(defn EVAL [expr binds]
(if (ATOM? expr)
(GET expr binds)
(if (ATOM? (FIRST expr))
(if (= (FIRST expr) `|quote|)
(SECOND expr)
(if (= (FIRST expr) `|atom?|)
(ATOM? (EVAL (SECOND expr) binds))
...
do defmacro stuff down here
...)))))
def
fn
apply
=
if
`
defmacro
deftype
defprotocol
reify
intern
.
defmulti
defmethod
defrecord
first
rest
[]
^
{}
delay
force
new
defclass
proxy
list*
fn*
fn?
seq
clojure.lang.RT
and so on...