SNAKE WRANGLING SNAKE WRANGLING Isaac Elliott How can we bring the - - PowerPoint PPT Presentation

snake wrangling snake wrangling
SMART_READER_LITE
LIVE PREVIEW

SNAKE WRANGLING SNAKE WRANGLING Isaac Elliott How can we bring the - - PowerPoint PPT Presentation

SNAKE WRANGLING SNAKE WRANGLING Isaac Elliott How can we bring the benefits of better languages to existing codebases? Language tooling Language tooling for Python append_to :: Statement append_to = def_ "append_to" [ p_


slide-1
SLIDE 1

SNAKE WRANGLING SNAKE WRANGLING

Isaac Elliott

slide-2
SLIDE 2

How can we bring the benefits of better languages to existing codebases?

slide-3
SLIDE 3

Language tooling

slide-4
SLIDE 4

Language tooling for Python

slide-5
SLIDE 5

append_to :: Statement append_to = def_ "append_to" [ p_ "element", k_ "to" (list_ []) ] [ expr_ $ call_ ("to" /> "append") [ "element" ] , return_ "to" ]

slide-6
SLIDE 6

def append_to(element, to=[]): to.append(element) return to

slide-7
SLIDE 7

def append_to(element, to=None): if to is None: to = [] to.append(element) return to

slide-8
SLIDE 8

fixMutableDefaultArguments :: Statement -> Maybe Statement

slide-9
SLIDE 9

rewriteOn _Statements fixMutableDefaultArguments :: Module -> Module

slide-10
SLIDE 10

DESIGN DESIGN

slide-11
SLIDE 11

Parse & Print

slide-12
SLIDE 12

(print . parse) :: String -> String = id

slide-13
SLIDE 13

Write & Check

slide-14
SLIDE 14

Optics

slide-15
SLIDE 15

PROPERTY TESTING PROPERTY TESTING

slide-16
SLIDE 16

Property of plus:

for all inputs A and B: A + B == B + A

slide-17
SLIDE 17

Parsing, printing, and validating Python source have useful properties

slide-18
SLIDE 18

print . parse = id

slide-19
SLIDE 19

Shrinking can find minimal counter-examples

slide-20
SLIDE 20

You don't need to remember the whole language

slide-21
SLIDE 21

Random generation is great for poking programming languages

slide-22
SLIDE 22

((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((

slide-23
SLIDE 23

CORRECTNESS BY CORRECTNESS BY CONSTRUCTION CONSTRUCTION

slide-24
SLIDE 24

If we can construct some data, then that data is correct (by some measure)

slide-25
SLIDE 25

Incorrect = type error

slide-26
SLIDE 26

Syntactically correct by construction

slide-27
SLIDE 27

Python syntax isn't very straightforward

slide-28
SLIDE 28

data Expr = Int Int | Bool Bool | Var String | ... data Statement = Assign Expr Expr | ...

slide-29
SLIDE 29

1 = 2

Assign (Int 1) (Int 2)

slide-30
SLIDE 30

data Expr = Int Int | Bool Bool | Var String | ... data AssignableExpr = AEVar String | ... data Statement = Assign AssignableExpr Expr | ...

slide-31
SLIDE 31

{-# language GADTs, DataKinds, KindSignatures #-}

slide-32
SLIDE 32

data Assignable = IsAssignable | NotAssignable data Expr :: Assignable -> * where Int :: Int -> Expr 'NotAssignable Bool :: Bool -> Expr 'NotAssignable Var :: String -> Expr a ... data Statement = Assign (Expr 'IsAssignable) (Expr 'NotAssignable) | ...

slide-33
SLIDE 33

expr :: Parser (Expr ??)

slide-34
SLIDE 34

exprAssignable :: Parser (Expr 'Assignable) exprNotAssignable :: Parser (Expr 'NotAssignable)

slide-35
SLIDE 35

data ExprU = IntU Int | BoolU Bool | VarU String | ... data StatementU = AssignU ExprU ExprU | ...

slide-36
SLIDE 36

expr :: Parser ExprU statement :: Parser StatementU

slide-37
SLIDE 37

validateExprAssignable :: ExprU

  • > Either SyntaxError (Expr 'Assignable)

validateExprNotAssignable :: ExprU

  • > Either SyntaxError (Expr 'NotAssignable)

validateStatement :: StatementU

  • > Either SyntaxError Statement
slide-38
SLIDE 38

Rinse and repeat

slide-39
SLIDE 39

But it (mostly) worked... until...

slide-40
SLIDE 40

not(condition)

slide-41
SLIDE 41

data Expr :: type_stuff -> * where Not :: {- not -} NonEmpty Whitespace

  • > Expr type_stuff
  • > Expr type_stuff

Parens :: Expr type_stuff

  • > Expr type_stuff

...

slide-42
SLIDE 42

NonEmpty Whitespace -> [Whitespace]

slide-43
SLIDE 43

data Expr :: type_stuff -> * where Not :: {- not -} [Whitespace]

  • > Expr type_stuff
  • > Expr type_stuff

Parens :: Expr type_stuff

  • > Expr type_stuff

...

slide-44
SLIDE 44

not(condition)

Not [] (Parens condition)

slide-45
SLIDE 45

notnot(condition)

Not [] (Not [] (Parens condition))

slide-46
SLIDE 46

Spaces are required between tokens when their concatenation would give a single token

slide-47
SLIDE 47

mkNot :: {- not -} [Whitespace]

  • > Expr type_stuff
  • > Either SyntaxError (Expr type_stuff)
slide-48
SLIDE 48

_Not :: Prism' Expr ([Whitespace], Expr)

slide-49
SLIDE 49

_Not :: Prism Expr ExprU ([Whitespace], Expr) ([Whitespace], ExprU)

slide-50
SLIDE 50

Expr -> ExprU

slide-51
SLIDE 51

CONCRETE SYNTAX CONCRETE SYNTAX TREE TREE

slide-52
SLIDE 52

Have the data structures mirror the syntax

slide-53
SLIDE 53
slide-54
SLIDE 54

Syntax is not code

slide-55
SLIDE 55

THE DRAWING THE DRAWING BOARD BOARD

slide-56
SLIDE 56

Correct by Construction

slide-57
SLIDE 57

Concrete Syntax Tree

slide-58
SLIDE 58

Validated/Unvalidated Trees

slide-59
SLIDE 59

data Expr (ts :: [*]) = Int Int | Bool Bool | Var String | Not (Expr ts) | ... data Statement (ts :: [*]) = Assign (Expr ts) (Expr ts) | ...

slide-60
SLIDE 60
  • - raw, unvalidated

Expr '[]

  • - syntax validated

Expr '[Syntax]

  • - indentation & syntax validated

Statement '[Indentation, Syntax]

slide-61
SLIDE 61

data Indentation validateStatementIndentation :: Statement ts

  • > Either SyntaxError (Statement (Nub (Indentation ': ts)))
slide-62
SLIDE 62

_Not :: Prism (Expr ts) (Expr '[]) ([Whitespace], Expr ts) ([Whitespace], Expr '[])

slide-63
SLIDE 63

import Data.Coerce unvalidateStatement :: Statement ts -> Statement '[] unvalidateStatement = coerce

slide-64
SLIDE 64

Making 'incorrect' things impossible vs. Making 'correct' things trivial

slide-65
SLIDE 65

Modelling how things appear vs. Modelling what things mean

slide-66
SLIDE 66

SUMMARY SUMMARY

slide-67
SLIDE 67

Property testing is great

slide-68
SLIDE 68

We can get pretty far with type-level programming...

slide-69
SLIDE 69

...But it's better to err on the side of usability

slide-70
SLIDE 70

Instead of making the incorrect impossible, make the correct trivial

slide-71
SLIDE 71

Figure out abstractions, rather than sticking to appearances

slide-72
SLIDE 72

Mistakes are probably necessary

slide-73
SLIDE 73

COOL STUFF COOL STUFF

slide-74
SLIDE 74

fact_tr :: Statement '[] fact_tr = def_ "fact" [p_ "n"] [ def_ "go" [p_ "n", p_ "acc"] [ ifElse_ ("n" .== 0) [return_ "acc"] [return_ $ call_ "go" [p_ $ "n" .- 1, p_ $ "n" .* "acc"]] ] , return_ $ call_ "go" [p_ "n", p_ 1] ]

slide-75
SLIDE 75

def fact(n): def go(n, acc): if n == 0: return acc else: return go(n - 1, n * acc) return go(n, 1)

slide-76
SLIDE 76
  • ptimizeTailRecursion :: Statement '[] -> Maybe (Statement '[])
slide-77
SLIDE 77

rewrite optimizeTailRecursion fact_r :: Statement '[] -> Statement '[]

slide-78
SLIDE 78

def fact(n): def go(n, acc): n__tr = n acc__tr = acc __res__tr = None while True: if n__tr == 0: __res__tr = acc__tr break else: n__tr__old = n__tr acc__tr__old = acc__tr n__tr = n__tr__old - 1 acc__tr = n__tr__old * acc__tr__old return __res__tr return go(n, 1)