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