On Github kanaka / midwest.io.mal
Midwest.io 2015
Joel Martin
Press 's' to show speaker notes
Joel Martin (kanaka)
Principal Software Engineer at ViaSat, Inc
Clojure
Satellites!
The Best Languages for the Organization Task/Project System
It's a Polyglot World
Language learning is part of the job.
The typical process:
"Work" is not a thing, it's a state of mind.
>>A quote from Charles Coonradt
Meat packers might act like they have the job from hell, but then those same people might gladly pay to go hunting in the dead of winter.
The fact is >> "Work" is not a thing.
...
function read_form(reader) {
var token = reader.peek();
switch (token) {
// reader macros/transforms
case ';': return null; // Ignore comments
case '\'': reader.next();
return [types._symbol('quote'), read_form(reader)];
case '`': reader.next();
return [types._symbol('quasiquote'), read_form(reader)];
case '~': reader.next();
return [types._symbol('unquote'), read_form(reader)];
...
First did an implementation in JS to make sure
I understood the process well. I had not made a Lisp
from scratch before.
...
define READ_FORM
$(and $(READER_DEBUG),$(info READ_FORM: $($(1))))
$(call READ_SPACES,$(1))
$(foreach ch,$(word 1,$($(1))),\
$(if $(filter $(SEMI),$(ch)),\
$(call DROP_UNTIL,$(1),$(_NL)),\
$(if $(filter $(SQUOTE),$(ch)),\
$(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
$(call _list,$(call _symbol,quote) $(strip $(call READ_FORM,$(1)))),\
$(if $(filter $(QQUOTE),$(ch)),\
$(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
$(call _list,$(call _symbol,quasiquote) $(strip $(call READ_FORM,$(1)))),\
$(if $(filter $(UNQUOTE),$(ch)),\
...
"Make Lisp"
cd make make -f ./mal.mk user> ( (fn* [a b] (* a b)) 7 8) less mal.mk
...
READ_FORM () {
local token=${__reader_tokens[${__reader_idx}]}
case "${token}" in
\') __reader_idx=$(( __reader_idx + 1 ))
_symbol quote; local q="${r}"
READ_FORM; local f="${r}"
_list "${q}" "${f}" ;;
\`) __reader_idx=$(( __reader_idx + 1 ))
_symbol quasiquote; local q="${r}"
READ_FORM; local f="${r}"
_list "${q}" "${f}" ;;
...
...
MalVal *read_form(Reader *reader) {
char *token;
MalVal *form = NULL, *tmp;
token = reader_peek(reader);
if (!token) { return NULL; }
switch (token[0]) {
case ';':
abort("comments not yet implemented");
break;
case '\'':
reader_next(reader);
form = _listX(2, malval_new_symbol("quote"),
read_form(reader));
break;
...
...
def read_form(reader):
token = reader.peek()
# reader macros/transforms
if token[0] == ';':
reader.next()
return None
elif token == '\'':
reader.next()
return _list(_symbol('quote'), read_form(reader))
elif token == '`':
reader.next()
return _list(_symbol('quasiquote'), read_form(reader))
...
...
;; Override some tools.reader reader macros so that we can do our own
;; metadata and quasiquote handling
(alter-var-root #'r/macros
(fn [f]
(fn [ch]
(case ch
\` (wrap 'quasiquote)
\~ (fn [rdr comma]
(if-let [ch (rt/peek-char rdr)]
(if (identical? \@ ch)
((wrap 'splice-unquote) (doto rdr rt/read-char) \@)
((wrap 'unquote) rdr \~))))
...
...
function read_form($reader) {
$token = $reader->peek();
switch ($token) {
case '\'': $reader->next();
return _list(_symbol('quote'),
read_form($reader));
case '`': $reader->next();
return _list(_symbol('quasiquote'),
read_form($reader));
case '~': $reader->next();
return _list(_symbol('unquote'),
read_form($reader));
...
...
public static MalVal read_form(Reader rdr)
throws MalContinue, ParseError {
String token = rdr.peek();
if (token == null) { throw new MalContinue(); }
MalVal form;
switch (token.charAt(0)) {
case '\'': rdr.next();
return new MalList(new MalSymbol("quote"),
read_form(rdr));
case '`': rdr.next();
return new MalList(new MalSymbol("quasiquote"),
read_form(rdr));
...
"Make Lisp" became "Make-A-Lisp"
Demo Time
cd ../python cat ../mal/hello.py
./mal.py ../mal/hello.py
cd ../bash ./mal.sh ../mal/clojurewest2014.mal
less ../mal/clojurewest2014.mal
cd ../js ./mal.js ../mal/mal.mal
less ../mal/mal.mal
...
% read_form: read the next form from string start at idx
/read_form { 3 dict begin
read_spaces
/idx exch def
/str exch def
idx str length ge { null str idx }{ %if EOF
/ch str idx get def % current character
ch 39 eq { %if '\''
/idx idx 1 add def
str idx read_form
3 -1 roll /quote exch 2 _list 3 1 roll
...
Yes, Postscript the typesetting language.
After my lightning talk at Clojure West somebody suggested Postscript and so I took up the challenge.
I had never written a line of code in a stack-based/concatenative language like Postscript
More challenging than most, not as difficult as GNU Make
...
public static MalVal read_form(Reader rdr) {
string token = rdr.peek();
if (token == null) { throw new MalContinue(); }
MalVal form = null;
switch (token) {
case "'": rdr.next();
return new MalList(new MalSymbol("quote"),
read_form(rdr));
case "`": rdr.next();
return new MalList(new MalSymbol("quasiquote"),
read_form(rdr));
...
...
def read_form(rdr)
return case rdr.peek
when ";" then nil
when "'" then rdr.next; List.new [:quote, read_form(rdr)]
when "`" then rdr.next; List.new [:quasiquote, read_form(rdr)]
when "~" then rdr.next; List.new [:unquote, read_form(rdr)]
when "~@" then rdr.next; List.new [:"splice-unquote", read_form(rdr)]
when "^" then rdr.next; meta = read_form(rdr);
List.new [:"with-meta", read_form(rdr), meta]
...
...
sub read_form {
my($rdr) = @_;
my $token = $rdr->peek();
given ($token) {
when("'") { $rdr->next(); List->new([Symbol->new('quote'),
read_form($rdr)]) }
when('`') { $rdr->next(); List->new([Symbol->new('quasiquote'),
read_form($rdr)]) }
when('~') { $rdr->next(); List->new([Symbol->new('unquote'),
read_form($rdr)]) }
...
...
func read_form(rdr Reader) (MalType, error) {
token := rdr.peek()
if token == nil {
return nil, errors.New("read_form underflow")
}
switch *token {
case `'`:
rdr.next()
form, e := read_form(rdr)
if e != nil {
return nil, e
}
return List{[]MalType{Symbol{"quote"}, form}, nil}, nil
...
...
fn read_form(rdr : &mut Reader) -> MalRet {
let otoken = rdr.peek();
let stoken = otoken.unwrap();
let token = &stoken[..];
match token {
"'" => {
let _ = rdr.next();
match read_form(rdr) {
Ok(f) => Ok(list(vec![symbol("quote"), f])),
Err(e) => Err(e),
}
},
...
More challenging then average, but it's a very interesting language
...
read_form <- function(rdr) {
token <- Reader.peek(rdr)
if (token == "'") {
. <- Reader.next(rdr);
new.list(new.symbol("quote"), read_form(rdr))
} else if (token == "`") {
. <- Reader.next(rdr);
new.list(new.symbol("quasiquote"), read_form(rdr))
} else if (token == "~") {
. <- Reader.next(rdr);
new.list(new.symbol("unquote"), read_form(rdr))
...
...
read_form = (rdr) ->
token = rdr.peek()
switch token
when '\'' then [_symbol('quote'), read_form(rdr.skip())]
when '`' then [_symbol('quasiquote'), read_form(rdr.skip())]
when '~' then [_symbol('unquote'), read_form(rdr.skip())]
when '~@' then [_symbol('splice-unquote'), read_form(rdr.skip())]
when '^'
meta = read_form(rdr.skip())
[_symbol('with-meta'), read_form(rdr), meta]
when '@' then [_symbol('deref'), read_form(rdr.skip())]
...
...
Shared Function read_form(rdr As Reader) As MalVal
Dim token As String = rdr.peek()
If token Is Nothing Then
throw New MalContinue()
End If
Dim form As MalVal = Nothing
Select token
Case "'"
rdr.get_next()
return New MalList(New MalSymbol("quote"),
read_form(rdr))
...
...
def read_form(rdr: Reader): Any = {
return rdr.peek() match {
case "'" => { rdr.next; _list(Symbol("quote"), read_form(rdr)) }
case "`" => { rdr.next; _list(Symbol("quasiquote"), read_form(rdr)) }
case "~" => { rdr.next; _list(Symbol("unquote"), read_form(rdr)) }
case "~@" => { rdr.next; _list(Symbol("splice-unquote"), read_form(rdr)) }
case "^" => { rdr.next; val meta = read_form(rdr);
_list(Symbol("with-meta"), read_form(rdr), meta) }
case "@" => { rdr.next; _list(Symbol("deref"), read_form(rdr)) }
...
...
read_form :: Parser MalVal
read_form = do
ignored
x <- read_macro
<|> read_list
<|> read_vector
<|> read_hash_map
<|> read_atom
return $ x
read_str :: String -> IOThrows MalVal
read_str str = case parse read_form "Mal" str of
...
...
(define (read_form rdr)
(let ([token (send rdr peek)])
(if (null? token)
(raise (make-blank-exn "blank line" (current-continuation-marks)))
(cond
[(equal? "'" token) (send rdr next) (list 'quote (read_form rdr))]
[(equal? "`" token) (send rdr next) (list 'quasiquote (read_form rdr))]
[(equal? "~" token) (send rdr next) (list 'unquote (read_form rdr))]
[(equal? "~@" token) (send rdr next) (list 'splice-unquote (read_form rdr))]
[(equal? "^" token) (send rdr next)
(let ([meta (read_form rdr)])
(list 'with-meta (read_form rdr) meta))]
...
...
function M.read_form(rdr)
local token = rdr:peek()
if "'" == token then
rdr:next()
return List:new({Symbol:new('quote'), M.read_form(rdr)})
elseif '`' == token then
rdr:next()
return List:new({Symbol:new('quasiquote'), M.read_form(rdr)})
elseif '~' == token then
rdr:next()
return List:new({Symbol:new('unquote'), M.read_form(rdr)})
...
...
and read_form all_tokens =
match all_tokens with
| [] -> raise End_of_file;
| token :: tokens ->
match token with
| "'" -> read_quote "quote" tokens
| "`" -> read_quote "quasiquote" tokens
| "~" -> read_quote "unquote" tokens
| "~@" -> read_quote "splice-unquote" tokens
| "@" -> read_quote "deref" tokens
...
Buzzword alert: Gamification
Make-A-Lisp is gamification of language learning
Measure of progress towards goal
Freedom to choose how to succeed
make test^MY_IMPL
make test^MY_IMPL^stepX