$30 off During Our Annual Pro Sale. View Details »

Writing a Lisp in Python

Writing a Lisp in Python

I gave a roughly 60-minute walkthrough of a Pythonic implementation of a subset of the Lisp programming language. An ancient, powerful beast was awakened.

Avatar for Brian Muhia

Brian Muhia

April 24, 2014
Tweet

Other Decks in Technology

Transcript

  1. About Brian... • Trained in Mathematics. • Python coder since

    2009, started out doing number theory and beginner crypto problem sets on Windows. • Lisper since 2009. Really got into it in 2011, got started coding simple text-adventure games.
  2. Why Lisp? “LISP is worth learning for a different reason

    — the profound enlightenment experience you will have when you finally get it. That experience will make you a better programmer for the rest of your days, even if you never actually use LISP itself a lot.” - Eric S. Raymond
  3. Why Lisp? • A working subset of Lisp can be

    implemented in ~90 lines of Python. • It has very little syntax, which is good.
  4. Syntax & Semantics • Syntax: What a language looks like.

    • Semantics: What correctly-written expressions in that language mean.
  5. Computing factorials... 5! = 5 x 4 x 3 x

    2 x 1 n! = n x (n – 1) x (n – 2) x … x 3 x 2 x 1 1! = 1 0! = 1 (define factorial (number) (if (<= number 1) 1 (* number (factorial (- number 1)))))
  6. Parser >> program = "(begin (define r 3) (* 3.141592653

    (* r r)))" >> parse(program) ['begin', ['define', 'r', 3], ['*', 3.141592653, ['*', 'r', 'r']]] >> eval(parse(program)) 28.274333877
  7. Our Interpreter Begins with the beautiful Miss Eva Lu Ator...

    def eval(x, env=global_env): "Evaluate an expression in an environment. The parameter env is a dictionary."
  8. Variable reference A symbol is interpreted as a variable name;

    its value is the variable's value. Example: x, var, my_name...
  9. Quotation Syntax: (quote exp) Semantics: Return the exp literally; do

    not evaluate it. Example: (quote (a b c)) => (a b c)
  10. Conditional • Syntax: (if test If-true if-false) • Semantics: Evaluate

    test; if true, return if-true. If false, return if-false. Example: (if (< 10 20) (+ 1 1) (+ 3 3)) 2 ⇒
  11. Code elif x[0] == 'if': (_, test, if_true, if_false) =

    x return eval((if_true if eval(test, env) else if_false), env)
  12. Assignment • Syntax: (set! var exp) • Semantics: Evaluate exp

    and assign that value to var, which must have been previously defined. The '!' is part of the name.
  13. Code elif x[0] == 'set!': (_, var, exp) = x

    env.find(var)[var] = eval(exp, env)
  14. Definition Syntax: (define var exp) Semantics: Define a new variable

    and give it the value of evaluating the expression exp. Example: (define r 3) or (define square (lambda (x) (* x x)))
  15. Procedure Syntax: (lambda (var...) exp) Semantics: Create a procedure with

    parameter(s) named var... and the exp as the body. Example: (lambda (r) (* 3.141592653 r r))
  16. Code elif x[0] == 'lambda': (_, vars, exp) = x

    return lambda *args: eval(exp, Env(vars, args, env))
  17. Sequencing Syntax: (begin exp...) Semantics: Evaluate each of the expressions,

    in left-to-right order, and return the final value. Example: (begin (set! x 1) (set! x (+ x 1)) (* x 2)) 4 ⇒
  18. Procedure call Syntax: (proc exp...) Semantics: If proc is anything

    other than the symbols if, set!, define, lambda, begin, or quote then it is treated as a procedure and is evaluated using the same rules defined above. Example: (square 12) 144 ⇒
  19. Code else: exps = [eval(exp, env) for exp in x]

    proc = exps.pop(0) return proc(*exps)
  20. Consider >> program = "(define area (lambda (r) (* 3.141592653

    (* r r)))" >>> parse(program) ['define', 'area', ['lambda', ['r'], ['*', 3.141592653, ['*', 'r', 'r']]]] >>> eval(parse(program)) None
  21. class Env(dict): "An environment: a dict of {'var':val} pairs, with

    an outer Env." def __init__(self, parms=(), args=(), outer=None): self.update(zip(parms,args)) self.outer = outer def find(self, var): "Find the innermost Env where var appears." return self if var in self else self.outer.find(var)
  22. (define make­account (lambda (balance) (lambda (amt) (begin (set! balance (+

    balance amt)) balance)))) (define a1 (make­account 100.00)) (a1 ­20.00)
  23. Lexical scoping The process of looking first in inner environments

    and then in outer ones in order to find bindings to referenced variables.
  24. def add_globals(env): "Add some Scheme standard procedures to an environment."

    import math, operator as op env.update(vars(math)) # sin, sqrt... env.update( {'+':op.add, '-':op.sub, '*':op.mul, '/':op.div, 'not':op.not_, '>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq, 'equal?':op.eq, 'eq?':op.is_, 'length':len, 'cons':lambda x,y:[x]+y, 'first':lambda x:x[0],'rest':lambda x:x[1:], 'append':op.add, 'list':lambda *x:list(x), 'list?': lambda x:isa(x,list), 'null?':lambda x:x==[], 'symbol?':lambda x: isa(x, Symbol)}) return env
  25. def tokenize(s): "Convert a string into a list of tokens."

    return s.replace('(',' ( ').replace(')',' ) ').split() >>> program = "(set! twox (* x 2))" >>> tokenize(program) # lexical analysis ['(', 'set!', 'twox', '(', '*', 'x', '2', ')', ')']
  26. def read(s): "Read a Scheme expression from a string." return

    read_from(tokenize(s)) parse = read >>> parse(program) ['set!', 'twox', ['*', 'x', 2]]
  27. def read_from(tokens): "Read an expression from a sequence of tokens."

    if len(tokens) == 0: raise SyntaxError('unexpected EOF while reading') token = tokens.pop(0) if '(' == token: L = [] while tokens[0] != ')': L.append(read_from(tokens)) tokens.pop(0) # pop off ')' return L elif ')' == token: raise SyntaxError('unexpected )') else: return atom(token)
  28. def atom(token): "Numbers become numbers; every other token is a

    symbol." try: return int(token) except ValueError: try: return float(token) except ValueError: return Symbol(token)
  29. def to_string(exp): "Convert a Python object back into a Lisp-

    readable string." return '(' + ' '.join(map(to_string,\ exp))+')' if isa(exp, list) else str(exp)
  30. def repl(prompt='lis.py> '): "A prompt read-eval-print loop." while True: val

    = eval(parse(raw_input(prompt))) if val is not None: print to_string(val)