Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Litil, Growing a functional language on the JVM

Litil, Growing a functional language on the JVM

Les slides de ma présentation de Litil, un petit langage fonctionnel sur JVM inspiré de ML lors d'une technozaure à Zenika.

Jawher Moussa

May 23, 2013
Tweet

More Decks by Jawher Moussa

Other Decks in Programming

Transcript

  1. LitiL affectations, expressions & fonctions let  five  =  3 let

     twice  x  =  3  *  x  -­‐  (42  -­‐  41)  *  x let  x2  =  twice  5 let  add  x  y  =  x  +  y let  seven  =  add  4  (1  +  2) let  fact  n  =    if  n  <  2  then        1    else        n  *  fact  (n-­‐1) Thursday, 23 May, 13
  2. LitiL Tuples et Listes let  x  =  (5,  "a",  true)

    let  pair  x  y  =  (x,  y) let  xs  =  [1,  2,  3] Thursday, 23 May, 13
  3. LitiL Deconstruction let  (a,  b)  =  (4,  "d") let  d

     =  ((4,  true),  ("test",  'c',  a)) let  ((_,  bool),  (_,  _,  _))  =  d Thursday, 23 May, 13
  4. LitiL ADTs data  Option  a  =  Some  a  |  None

    let  o  =  Some  "thing" data  List  a  =  Cons  a  (List  a)  |  Nil let  l  =  Cons  5  (Cons  6  Nil) data  Tree  a  =  Null  |  Leaf  a  |  Node  (Tree  a)  a  (Tree  a) let  t  =  Node  (Leaf  5)  4  (Leaf  3) Thursday, 23 May, 13
  5. LitiL Application partielle let  add  x  y  =  x  +

     y let  inc  =  add  1 let  three  =  inc  2 Thursday, 23 May, 13
  6. LitiL Lambdas & closures let  map  f  xs  =  

     match  xs        []          =>  Nil        h  ::  t  =>  (f  h)  ::  (map  f  t) let  l  =  [1,  2] let  double  x  =  2  *  x -­‐-­‐  pass  a  function  by  name let  l2  =  map  double  l -­‐-­‐  or  simply  a  lambda let  l2  =  map  (\x  =>  2  *  x)  l let  a  =  4 let  f  =  \x  =>  a  *  x  -­‐-­‐  f  captures  the  lexical  value  of  a,  i.e.  4 let  a  =  5 f  5 Thursday, 23 May, 13
  7. Lexer :: Token •Type: SYM,  NAME,  KEYWORD,  ... •Valeur: ‘+’,

     ‘x’,  ‘let’ •ligne, colonne Thursday, 23 May, 13
  8. Exemple let  five  =  3 KEYWORD let NAME five SYM

    = NUM 3 EOF $ Thursday, 23 May, 13
  9. Algo pop()  :  Token c  =  nextChar() if  c  =

     \n  return  blackMagic() elif  c  :  [0-­‐9]  return  readNum() elif  c  =  “  return  readString() elif  c  =  ‘  return  readString() elif  c  in  symsRoots  return  readSym() elif  return  readNameOrKwOrBool() Thursday, 23 May, 13
  10. Les blancs ... ligne1 ligne2 NEWLINE ligne1 ligne2 INDENT +

    NEWLINE ligne1 ligne2 DEINDENT + NEWLINE Thursday, 23 May, 13
  11. Exemple let  add  x  y  =  x  +  y KEYWORD

    let NAME add NAME x NAME y SYM = SYM + NAME x NAME y Expr.EName name:  y LetBinding name:  add type:_ args:  [x,  y] instructions EAp fn arg EAp fn arg Expr.EName name:  + Expr.EName name:  x Thursday, 23 May, 13
  12. BNF let  :  “let”  id  args  “=”  body args:  id

     args  |  ε id:  [a-­‐zA-­‐Z]+[a-­‐zA-­‐Z0-­‐9]* body:  ... Thursday, 23 May, 13
  13. EBNF •Augmente BNF avec les opérateurs “+”, “*” et “?”

    •Grammaires plus compactes Thursday, 23 May, 13
  14. EBNF let  :  “let”  id  args  “=”  body args:  id*

    id:  [a-­‐zA-­‐Z]+[a-­‐zA-­‐Z0-­‐9]* body:  ... Thursday, 23 May, 13
  15. RDP •Traduction EBNF vers le code •Règle => méthode •Référence

    à un non terminal => appel d’une méthode Thursday, 23 May, 13
  16. RDP public  LetBinding  let()  { expect(KEYWORD,  “let”); String  name  =

     name(); List<String>  args  =  args(); expect(SYM,  “=”); Body  body  =  body(); return  new  LetBinding(name,  args,  body); } Thursday, 23 May, 13
  17. EBNF <-> RDP let  :  “let”  id  args  “=”  

    body args:  id* id:  [a-­‐zA-­‐Z]+[a-­‐zA-­‐ Z0-­‐9]* body:  ... public  LetBinding  let()  { expect(KEYWORD,  “let”); String  name  =  name(); List<Named>  args  =  args(); expect(SYM,  “=”); Body  body  =  body(); return  new  LetBinding(name,  args,   body); } Thursday, 23 May, 13
  18. Parseur RDP •Un champ token •Méthodes expect(Token.Type) et expect(Token.Type, String

    value) •Méthodes found(Token.Type) et found(Token.Type, String value) Thursday, 23 May, 13
  19. LitiL Litil::RDP::1 public  Program  program()  {        Program

     p  =  new  Program();        p.instructions.addAll(body());        return  p; } private  List<Instruction>  body()  {        List<Instruction>  res  =  new  ArrayList<Instruction>();        while  (found(Token.Type.NEWLINE))  {                res.add(instruction());        }        return  res; } Thursday, 23 May, 13
  20. LitiL Litil::RDP::instruction private  Instruction  instruction()  {        if

     (found(Token.Type.KEYWORD,  "let"))  {                return  letBinding();        }  else  if  (found(Token.Type.KEYWORD,  "data"))  {                return  dataDecl();        }  else  if  (found(Token.Type.KEYWORD,  "exception"))  {                return  exceptionDecl();        }  else  {                return  expr();        } } Thursday, 23 May, 13
  21. LitiL Litil::RDP::let private  LetBinding  letBinding()  {        expect(Token.Type.NAME);

           String  functionName  =  token.text;        Type  functionReturnType  =  null;        List<Named>  args  =  params();        List<Type>  argTypes  =  new  ArrayList<Type>(args.size());        for  (Named  arg  :  args)  {                argTypes.add(arg.type);        }        if  (found(Token.Type.SYM,  ":"))  {                functionReturnType  =  type();                Type  letType  =  argTypes.isEmpty()  ?  functionReturnType  :   Type.Function(argTypes,  functionReturnType);        }        expect(Token.Type.SYM,  "=");        List<Instruction>  instructions  =  bloc();        LetBinding  letBinding  =  new  LetBinding(functionName,   functionReturnType,  args,  instructions);        return  letBinding; } Thursday, 23 May, 13
  22. LitiL Litil::RDP::match if  (found(Token.Type.KEYWORD,  "match"))  {        Expr

     input  =  expr(0);        expect(Token.Type.INDENT);        List<Expr.PatterMatching.Case>  cases  =  new   ArrayList<Expr.PatterMatching.Case>();        while  (found(Token.Type.NEWLINE))  {                cases.add(patMatchCase());        }        expect(Token.Type.DEINDENT);        return  new  Expr.PatterMatching(input,  cases); } Thursday, 23 May, 13
  23. Priorité expr  :  expr  op  expr   |  [0-­‐9]+ op:

     “+”  |  “-­‐”  |  “*”  |  “/” 1  +  2  *  3  =  ? Thursday, 23 May, 13
  24. Priorité WTF ? 1  +  2  *  3 =  9

    W AT ? Thursday, 23 May, 13
  25. Because 1  +  2  *  3 val op expr val

    op val Thursday, 23 May, 13
  26. Du coup •Encoder les règles de précédence dans la grammaire

    •En introduisant des productions intermédiaires Thursday, 23 May, 13
  27. EBNF Révisé expr  :  expr  (“+”|”-­‐”)  prod  |  prod prod:

     prod  (“*”|”/”)  val  |  val val:  [0-­‐9]+ Thursday, 23 May, 13
  28. Mais avec N opérateurs •On finit avec beaucoup de niveaux

    intermédiaires •Et donc avec autant de niveaux dans la stack avec le RDP Thursday, 23 May, 13
  29. Prattman pour nous sauver ! •Parser les expressions en un

    temps linéaire •Des stacks beaucoup moins profondes (fonction du nombre d’opérateurs) Thursday, 23 May, 13
  30. Pratt parser •Opère sur les tokens pas les règles •Chaque

    token a une priorité •lbp(+) = 10 et lbp(*) = 20 par exemple •handler pour les tokens “infix”: led •nud pour les “prefix” Thursday, 23 May, 13
  31. LitiL Priorités LBP.put("and",  5); LBP.put("or",  5); LBP.put("not",  7); LBP.put("=",  7);

    LBP.put("<",  7); LBP.put(">",  7); LBP.put("::",  11); LBP.put("+",  15); LBP.put("-­‐",  15); LBP.put("%",  20); LBP.put("*",  20); LBP.put("/",  20); LBP.put("[",  90); LBP.put("]",  1); LBP.put("(",  100); LBP.put(")",  1); Thursday, 23 May, 13
  32. LitiL Pratt parser :: expr public  Expr  expr(int  rbp)  {

           Token  tk  =  advance();        Expr  left  =  nud(tk);        while  (rbp  <  lbp(lexer.peek(1)))  {                tk  =  advance();                left  =  led(left,  tk);        }        return  left; } Thursday, 23 May, 13
  33. LitiL Pratt parser :: nud private  Expr  nud(Token  tk)  {

           if  (is(tk,  Token.Type.SYM,  "-­‐"))  {                Expr  expr  =  expr(100);                return  new  Expr.EAp(new  Expr.EName("-­‐/1"),  expr);        }  else  if  (is(tk,  Token.Type.NUM))  {                return  new  Expr.ENum(Integer.parseInt(tk.text));        }  else  if  (is(tk,  Token.Type.BOOL))  {                return  new  Expr.EBool(Boolean.parseBoolean(tk.text));        }  else  if  (is(tk,  Token.Type.NAME))  {                return  new  Expr.EName(tk.text);        } } Thursday, 23 May, 13
  34. LitiL Pratt parser :: led private  Expr  led(Expr  left,  Token

     tk)  {        int  bp  =  lbp(tk);        if  (is(tk,  Token.Type.SYM,  "+")  ||  is(tk,  Token.Type.SYM,  "*"))  {                Expr  right  =  expr(bp);  return  new  Expr.EAp( new  Expr.EAp(new  Expr.EName(tk.text),  left), right);        }  else  if  (left  instanceof  Expr.EName)  {                return  new  Expr.EAp(left,  nud(tk));        }  else  {                throw  error("Unexpected  token  "  +  tk);        } } Thursday, 23 May, 13
  35. Type checker •Plusieures possiblités: •Typage explicite •Typage mono-mophique avec inférence

    •Typage poly-morphique avec inférence (HM) Thursday, 23 May, 13
  36. Types “natifs” •Num  =  Oper(“Num”,  []) •Bool  =  Oper(“Bool”,  [])

    •Char  =  Oper(“Char”,  []) •String  =  Oper(“List”,  [Type.Char]) •... Thursday, 23 May, 13
  37. Type checker •Etant donné: •Un noeud de l’AST •Et un

    environnement de types •Retourner le type du noeud •Ou exception en cas de problème Thursday, 23 May, 13
  38. Typage explicite •Pas d’inférence •On vérifie que tous les types

    sont renseignés •Et qu’ils sont compatibles Thursday, 23 May, 13
  39. LitiL Typage explicite let  max  (x:  Num)  (y:  Num)  :

     Num  =   if  x  >  y     x   else     y • Vérifier que les arguments (x, y) déclarent leur type • Vérifier que le type de retour est précisé •Vérifier que le type de la condition de if est Bool • Vérifier que le type de retour du “then” est le même que celui déclaré comme retour • Idem pour le “else” Thursday, 23 May, 13
  40. LitiL Typage statique :: impl public  Type  analyze(AstNode  node,  TypeScope

     env)  {        if  (node  instanceof  Expr.ENum)  {                return  Type.INT;        }  else  if  (node  instanceof  Expr.EName)  {                Type  res  =  env.get(((Expr.EName)  node).name);                if  (res  ==  null)                        throw  new  TypeError("Undeclared  entity  '"  +  node  +  "'");                return  res;        }  else  if  (node  instanceof  Expr.EIf)  {                Expr.EIf  eif  =  (Expr.EIf)  node;                Type  condType  =  analyze(eif.cond,  env);                unify(condType,  Type.BOOL);                Type  thenType  =  null;                for  (Instruction  instr  :  eif.thenInstructions)                        thenType  =  analyze(instr,  env);                Type  elseType  =  null;                for  (Instruction  instr  :  eif.elseInstructions)                        elseType  =  analyze(instr,  env);                unify(thenType,  elseType);                return  thenType;        } } Thursday, 23 May, 13
  41. mono-morphisme let  first  x  y  =  x let  f  =

     first  4  false “first” sera typé: Num  -­‐>  Bool  -­‐>  Num Et non pas a  -­‐>  b  -­‐>  a static  <X,  Y>  X  first(X  x,  Y  y)  {   return  x } Thursday, 23 May, 13
  42. Type variable •Type qu’on cherche à déterminer •identifié par un

    nom généré automatiquement •Peut être “remplacée” par un autre type Thursday, 23 May, 13
  43. Unification •Etant donné 2 types •On cherche à vérifier qu’ils

    sont compatibles/équivalents •En effectuant de l’inférence s’il le faut Thursday, 23 May, 13
  44. Unify(t1, t2) ‣ Si  t1  est  une  variable  =>  t1

     inféré  à   t2 ‣ Si  t2  variable,  unifier  t2  et  t1  (cas   1) ‣ Sinon,  t1  et  t2  sont  du  type  Oper ‣ Si  t1.name  !=  t2.name  =>  erreur ‣ Si  t1.types.size  !=  t2  =>  erreur ‣ Unifier  t1.types  et  t2.types Thursday, 23 May, 13
  45. Algo •Même que typage explicite •Sans la vérif de la

    présence obligatoire des déclarations de types •unify qui effectue l’inférence Thursday, 23 May, 13
  46. LitiL Typage monomorphique :: impl public  Type  analyze(AstNode  node,  TypeScope

     env)  {        if  (node  instanceof  Expr.ENum)  {                return  Type.INT;        }  else  if  (node  instanceof  Expr.EName)  {                Type  res  =  env.get(((Expr.EName)  node).name);                if  (res  ==  null)                        throw  new  TypeError("Undeclared  entity  '"  +  node  +  "'");                return  res;        }  else  if  (node  instanceof  Expr.EIf)  {                Expr.EIf  eif  =  (Expr.EIf)  node;                Type  condType  =  analyze(eif.cond,  env);                unify(condType,  Type.BOOL);                Type  thenType  =  null;                for  (Instruction  instr  :  eif.thenInstructions)                        thenType  =  analyze(instr,  env);                Type  elseType  =  null;                for  (Instruction  instr  :  eif.elseInstructions)                        elseType  =  analyze(instr,  env);                unify(thenType,  elseType);                return  thenType;        } } Thursday, 23 May, 13
  47. LitiL Typage monomorphique :: impl :: let if  (node  instanceof

     LetBinding)  {        LetBinding  let  =  (LetBinding)  node;        TypeScope  ss  =  new  TypeScope(scope);        List<Type>  argTypes  =  new  ArrayList<Type>();        ss.define(let.name,  new  Type.Variable());        for  (Named  arg  :  let.args)  {                Type  argType  =  new  Type.Variable();                argTypes.add(argType);                ss.define(arg.name,  argType);        }        Type  resultType  =  null;        for  (Instruction  instr  :  let.instructions)  {                resultType  =  analyze(instr,  ss);        }        if  (let.type  !=  null)  {                unify(resultType,  let.type);        }        Type  letType  =  Type.Function(argTypes,  resultType);        scope.define(let.name,  letType);        return  letType; } Thursday, 23 May, 13
  48. TypeChecker poly- morphique •Peut inférer les types non présents de

    façon globale •Trouve systématiquement le type le plus général et plus générique •Utilise L’algo W de Hindley-Milner Thursday, 23 May, 13
  49. poly-morphisme let  first  x  y  =  x let  f  =

     first  4  false “first” sera typé: a  -­‐>  b  -­‐>  a Et non pas Num  -­‐>  Bool  -­‐>  Num static  <X,  Y>  X  first(X  x,  Y  y)  {   return  x } Thursday, 23 May, 13
  50. L’astuce ? •Dupliquer les variables d’un type à chaque unification

    •Ainsi, on garde la forme la plus générale Thursday, 23 May, 13
  51. Exemple • analyze(let  first  ...) • Types  {first=v1,  x=v2,  y=v3}

    • Au  final,  first:  a  -­‐>  b  -­‐>  a • analyze(let  f  =  ...) • unify(Num  -­‐>  Bool  -­‐>  v4,  a2  -­‐>  b2  -­‐>  a2) • a2  =  Num,  b2  =  Bool • v4  =  a2  =  Num • f:  Num • mais  first  est  toujours  a  -­‐>  b  -­‐>  c let  first  x  y  =  x let  f  =  first  4  false Fresh Thursday, 23 May, 13
  52. I lied •C’est plus compliqué que ça en fait ...

    •Ne pas dupliquer systématiquement •Sinon on va laisser passer des erreurs Thursday, 23 May, 13
  53. Exemple let  f  x  =   let  y  =  x

     +  1  -­‐-­‐  x  inferred  to  be  Num   if  x  -­‐-­‐  x  inferred  to  be  Bool     2   else     3 Thursday, 23 May, 13
  54. Donc •Ne pas dupliquer les arguments et le type de

    retour d’un let dans sa définition •Celui de l’expression du match dans les cases •etc. Thursday, 23 May, 13
  55. Bref •Hindley et Milner s’en sont déjà occupé •Tout que

    j’avais à faire c’était de: •Décoder les papiers de recherche •Adapter à Litil: •Pas de pattern-matching dans HM •Let différent •... Thursday, 23 May, 13
  56. Comment ? •Etant donné: •Un noeud de l’AST •Et un

    environnement de valeurs •Retourner le resultat de l’évaluation du noeud •Ou exception en cas de problème Thursday, 23 May, 13
  57. LitiL Expr :: Name public  Object  eval(AstNode  node,  ValScope  scope)

     {        if  (node  instanceof  Expr)  {                if  (node  instanceof  Expr.EName)  {                        Object  val  =  scope.get(((Expr.EName)  node).name);                        if  (val  ==  null)  {                                throw  new  EvalException("Unknwon  identifier  "  +  node);                        }  else  {                                return  val;                        }                } Thursday, 23 May, 13
  58. LitiL Expr :: Types simples public  Object  eval(AstNode  node,  ValScope

     scope)  {        else  if  (node  instanceof  Expr.ENum)  {                return  ((Expr.ENum)  node).value;        }  else  if  (node  instanceof  Expr.EBool)  {                return  ((Expr.EBool)  node).value;        }  else  if  (node  instanceof  Expr.EChar)  {                return  ((Expr.EChar)  node).value;        }  ... Thursday, 23 May, 13
  59. LitiL Expr :: Tuples public  Object  eval(AstNode  node,  ValScope  scope)

     {        if  (node  instanceof  Expr.ETuple)  {                Expr.ETuple  tuple  =  ((Expr.ETuple)  node);                List<Object>  res  =  new  ArrayList<Object>();                for  (Expr  value  :  tuple.values)  {                        res.add(eval(value,  scope));                }                return  res;        } Thursday, 23 May, 13
  60. LitiL Expr :: If if  (node  instanceof  Expr.EIf)  {  

         Expr.EIf  eif  =  (Expr.EIf)  node;        Object  cond  =  eval(eif.cond,  scope);        if  (cond  instanceof  Boolean)  {                if  ((Boolean)  cond)  {                        Object  val  =  null;                        ValScope  thenScope  =  scope.child();                        for  (Instruction  instr  :  eif.thenInstructions)  {                                val  =  eval(instr,  thenScope);                        }                        return  val;                }  else  {                        Object  val  =  null;                        ValScope  elseScope  =  scope.child();                        for  (Instruction  instr  :  eif.elseInstructions)  {                                try  {                                        val  =  eval(instr,  elseScope);                                }  catch  (LitilException  e)  {                                        //erreur                                }                        }                        return  val;                }        }  else  ... Thursday, 23 May, 13
  61. LitiL Expr :: Let if  (node  instanceof  LetBinding)  {  

         LetBinding  let  =  (LetBinding)  node;        ValScope  letScope  =  scope.child();        Fn  fn  =  null;        for  (int  i  =  let.args.size()  -­‐  1;  i  >=  0;  i-­‐-­‐)  {                Named  arg  =  let.args.get(i);                if  (i  ==  let.args.size()  -­‐  1)  {//last  argument                        fn  =  new  ChaininLastFn(arg.name,  let.instructions);                }  else  {                        fn  =  new  ChainingFn(arg.name,  fn);                }        }        fn  =  new  EnvCapturingFnWrapper(fn,  let,  scope);        scope.define(let.name,  fn);        return  fn; } Thursday, 23 May, 13
  62. LitiL Expr :: Ap if  (node  instanceof  Expr.EAp)  {  

         ValScope  ss  =  scope.child();        Expr.EAp  ap  =  (Expr.EAp)  node;        Object  fn  =  eval(ap.fn,  ss);        if  (fn  instanceof  Fn)  {                Object  evalArg  =  eval(ap.arg,  ss);                return  ((Fn)  fn).eval(evalArg,  ss);        } } Thursday, 23 May, 13
  63. LitiL What the Fn ? public  interface  Fn  {  

         public  Object  eval(Object  arg,  ValScope  scope); } ChainingFn argName nextFn eval: •Reprend les arguments précédents {name, value} •Rajoute le sien (arg) de façon anonyme •Retourne une implem de Fn (argument arg2) qui: •Reprend l’arg anonyme et l’indexe sous argName •Done la main à next ChainingLastFn argName instructions eval: •Reprend les arguments précédents {name, value} •Reprend l’arg anonyme et l’indexe sous argName •Prépare un environnement avec les args •Exécute instructions •Retourne le résultat Thursday, 23 May, 13
  64. LitiL And suddently boxes ! let  add  x  y  =

     x  +  y Expr.EName name:  y LetBinding name:  add type:_ args:  [x,  y] instructions EAp fn arg EAp fn arg Expr.EName name:  + Expr.EName name:  x AST add  1  2 ChainingFn argName:  x next ChainingLastFn argName:  y instructions Trop com pliqué* * En fait j’avais plus de temps pour préparer les schémas, et puis ça fait longtemps que j’avais pas touché à ça Thursday, 23 May, 13
  65. La suite •Refaire la partie records •Compilation vers bytecode (rapide)

    •Typeclasses, exhaustivité patmat, GADT ? •Intégration Java •Inception •A moi broadway ! Thursday, 23 May, 13