Slide 1

Slide 1 text

Applicative Style Command Line Parsing @markhibberd

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Who let the programmers loose?

Slide 4

Slide 4 text

! 1 case class Person(name: String, age: Int) 2 3 object App { 4 def main(args: Array[String]): Unit = { 5 val dfault = Person("something-invalid", -1) 6 val parser = Parser[Args] 7 .option[String]("--name", (a: Args, s: String) => a.copy(name = s)) 8 .option[Int]("--age", (a: Args, n: Int) => a.copy(age = n)) 9 parser.dispatch(p, dfault) { p => 10 /* not really winning */ 11 } 12 } 13 } !

Slide 5

Slide 5 text

! 1 case class Person(name: String, age: Int) 2 3 object App { 4 def main(args: Array[String]): Unit = { 5 val dfault = Person("something-invalid", -1) 6 val parser = Parser[Args] 7 .option[String]("--name", (a: Args, s: String) => a.copy(name = s)) 8 .option[Int]("--age", (a: Args, n: Int) => a.copy(age = n)) 9 parser.dispatch(p, dfault) { p => 10 /* not really winning */ 11 } 12 } 13 } ! making things up

Slide 6

Slide 6 text

! 1 case class Person(name: String, age: Int) 2 3 object App { 4 def main(args: Array[String]): Unit = { 5 val dfault = Person("something-invalid", -1) 6 val parser = Parser[Args] 7 .option[String]("--name", (a: Args, s: String) => a.copy(name = s)) 8 .option[Int]("--age", (a: Args, n: Int) => a.copy(age = n)) 9 parser.dispatch(p, dfault) { p => 10 /* not really winning */ 11 } 12 } 13 } ! tight coupling

Slide 7

Slide 7 text

! 1 case class Person(name: String, age: Int) 2 3 object App { 4 def main(args: Array[String]): Unit = { 5 val dfault = Person("something-invalid", -1) 6 val parser = Parser[Args] 7 .option[String]("--name", (a: Args, s: String) => a.copy(name = s)) 8 .option[Int]("--age", (a: Args, n: Int) => a.copy(age = n)) 9 parser.dispatch(p, dfault) { p => 10 /* not really winning */ 11 } 12 } 13 } ! unsafe

Slide 8

Slide 8 text

Yes this is made-up - but I assure you, reality is far worse

Slide 9

Slide 9 text

Who let the functional programmers loose?

Slide 10

Slide 10 text

! 1 case class Parse[A](parse: List[String] => ParseError \/ (List[String], A)) { 2 def map[B](f: A => B): Parse[B] = ??? 3 def flatMap[B](f: A => Parse[B]): Parse[B] = ??? 4 def |||(that: => Parse[A]): Parse[A] = ??? 5 }

Slide 11

Slide 11 text

! 1 case class Person(name: String, age: Int) 2 3 object App { 4 def main(args: Array[String]): Unit = { 5 val parser = for { 6 name <- option[String]("--name") 7 age <- option[Int]("--age") 8 } yield Person(name, age) 9 10 parser.dispatch(args) { x => 11 /* not really winning */ 12 } 13 } 14 }

Slide 12

Slide 12 text

! 1 case class Person(name: String, age: Int) 2 3 object App { 4 def main(args: Array[String]): Unit = { 5 val parser = for { 6 name <- option[String]("--name") 7 age <- option[Int]("--age") 8 } yield Person(name, age) 9 10 parser.dispatch(args) { x => 11 /* not really winning */ 12 } 13 } 14 } this is actually better

Slide 13

Slide 13 text

! 1 case class Person(name: String, age: Int) 2 3 object App { 4 def main(args: Array[String]): Unit = { 5 val parser = for { 6 name <- option[String]("--name") 7 age <- option[Int]("--age") 8 } yield Person(name, age) 9 10 parser.dispatch(args) { x => 11 /* not really winning */ 12 } 13 } 14 } ! but the price is too high

Slide 14

Slide 14 text

name

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

flatMap

Slide 18

Slide 18 text

! => age

Slide 19

Slide 19 text

! =>

Slide 20

Slide 20 text

! =>

Slide 21

Slide 21 text

! => ! => person

Slide 22

Slide 22 text

Monads are for Beginners

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

! 1 def pure (v: A) : F[A] ! 2 def map (f: A => B )(v: F[A]) : F[B] // Functor ! 3 def flatMap (f: A => F[B])(v: F[A]) : F[B] // Monad ! 4 def ap (f: F[A => B])(v: F[A]) : F[B] // Applicative

Slide 25

Slide 25 text

age name

Slide 26

Slide 26 text

age ! map Person name

Slide 27

Slide 27 text

age => Person age ! map Person name

Slide 28

Slide 28 text

age => Person age ! ap ! map Person name

Slide 29

Slide 29 text

person age => Person age ! ap ! map Person name

Slide 30

Slide 30 text

! 1 sealed trait Name 2 case class Short(s: Char) extends Name 3 case class Long(l: String) extends Name 4 case class Both(s: Char, l: String) extends Name 5 6 sealed trait Parser[A] 7 case class SwitchParser[A](name: Name, a: A) extends Parser[A] 8 case class FlagParser[A](name: Name, read: Read[A]) extends Parser[A] 9 case class ArgParser[A](p: Read[A]) extends Parser[A] 10 case class SubCommandParser[A](p: Read[A]) extends Parser[A]

Slide 31

Slide 31 text

! 1 sealed trait ParserA[A] 2 case class Empty[A]() extends ParserA[A] 3 case class Value[A](a: A) extends ParserA[A] 4 case class Parse[A](p: Parser[A], meta: Meta) extends ParserA[A] 5 case class Ap[A, B](p: ParserA[A => B], a: ParserA[A]) extends ParserA[B] 6 case class Alt[A](a: ParserA[A], b: ParserA[A]) extends ParserA[A]

Slide 32

Slide 32 text

! 1 def switch( n: Name): ParserA[Boolean] = 2 Parse(SwitchParser(n, true), 3 Meta(None, true)) ||| Value(false) 4 5 def option[A: Read](n: Name, meta: String): ParserA[A] = 6 Parse(FlagParser(n, List(meta), Read.of[A]), 7 Meta(None, true)) ||| Empty()

Slide 33

Slide 33 text

|*|

Slide 34

Slide 34 text

f |*| (a, b, c) ~> map -> ap -> ap

Slide 35

Slide 35 text

f |*| (a, b, c) ~> (a |@| b |@| c)(f)

Slide 36

Slide 36 text

f |*| (a, b, c) ~> ^(a, b, c)(f)

Slide 37

Slide 37 text

f |*| (a, b, c) ~> f <$> a <*> b <*> c

Slide 38

Slide 38 text

! 1 case class Person(awesome: Boolean, name: String, age: Int) 2 3 val parser: ParserA[Person] = Person |*| ( 4 switch(Long("awesome")) 5 , option(Long("name")) 6 , option(Long("age")) 7 )

Slide 39

Slide 39 text

person age => Person age ! ap ! map Person name

Slide 40

Slide 40 text

1 trait Traversal[A] { 2 def run[X](p: Parser[X], meta: Meta): A 3 } 4 5 def traversal[A, B](p: ParserA[A], f: Traversal[B]): List[B] = p match { 6 case Empty() => Nil 7 case Value(_) => Nil 8 case Parse(p, m) => List(f.run(p, m)) 9 case Ap(p, a) => traversal(p, f) ++ traversal(a, f) 10 case Alt(p1, p2) => traversal(p1, f) ++ traversal(p2, f) 11 }

Slide 41

Slide 41 text

1 case class Info(....) 2 3 object Info { implicit def InfoMonoid: Monoid[Info] = ??? } 4 5 class Usage extends Traversal[Info] { 6 def run[X](p: Parser[X], meta: Meta): A = p match { 7 case SwitchParser(...) => 8 case FlagParser(...) => 9 case ArgParser(...) => 10 case SubCommandParser(...) => 11 } 12 } 13 14 object Usage { 15 def get[A](p: ParserA[A]): Info = 16 traverse(p, new Usage).suml 17 }

Slide 42

Slide 42 text

1 trait Search[F[_], A] { 2 def run[X](p: Parser[X]): F[A] 3 } 4 5 def search[F[_}: MonadPlus, A](p: ParserA[A], f: Search[A]) : F[ParserA[A]] = 6 p match { 7 case Empty() => 8 MonadPlus[F].empty 9 case Value(_) => 10 MonadPlus[F].empty 11 case Parse(p, m) => 12 f.run(p).map(_.pure[ParserA]) 13 case Ap(p, a) => 14 search(p, f).flatMap(x => (a <*> x).pure[F]) <|> 15 search(p, a).flatMap(x => (x <*> k).pure[F]) 16 case Alt(p1, p2) => 17 search(f, p1) <+> search(f, p2) 18 }

Slide 43

Slide 43 text

Code