Top Banner
First-Class Patterns John A. De Goes - @jdegoes Frontier Developers, February 26
30

First-Class Patterns

Apr 22, 2015

Download

Technology

John De Goes

Some languages, like SML, Haskell, and Scala, have built-in support for pattern matching, which is a generic way of branching based on the structure of data.

While not without its drawbacks, pattern matching can help eliminate a lot of boilerplate, and it's often cited as a reason why functional programming languages are so concise.

In this talk, John A. De Goes talks about the differences between built-in patterns, and so-called first-class patterns (which are "do-it-yourself" patterns implemented using other language features).

Unlike built-in patterns, first-class patterns aren't magical, so you can store them in variables and combine them in lots of interesting ways that aren't always possible with built-in patterns. In addition, almost every programming language can support first-class patterns (albeit with differing levels of effort and type-safety).

During the talk, you'll watch as a mini-pattern matching library is developed, and have the opportunity to follow along and build your own pattern matching library in the language of your choice.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: First-Class Patterns

First-Class PatternsJohn A. De Goes - @jdegoes

Frontier Developers, February 26

Page 2: First-Class Patterns

Agenda● Intro● Pattern Matching 101● First-Class-ness 101● Magical Patterns● First-Class Patterns● Exercises

Page 3: First-Class Patterns

IntroPattern Matching

● Divides a (possibly infinite) set of values into a discrete number of cases, where each case can be handled in a uniform way

● “if” on steroids○ Sometimes strictly more powerful (e.g. Haskell)

Page 4: First-Class Patterns

Intro - Examples-- sign of a number

sign x | x > 0 = 1 | x == 0 = 0 | x < 0 = -1

-- take the first n elements from a list

take 0 _ = []take _ [] = []take n (x:xs) = x : take (n-1) xs

-- generate some javascript

valueToJs :: Options -> ModuleName -> Environment -> Value -> JSvalueToJs _ _ _ (NumericLiteral n) = JSNumericLiteral nvalueToJs _ _ _ (StringLiteral s) = JSStringLiteral svalueToJs _ _ _ (BooleanLiteral b) = JSBooleanLiteral b

...

Page 5: First-Class Patterns

Intro - Examplessealed trait Level

case object Level1 extends Level

case object Level2 extends Level

sealed trait Title

case object DBAdmin extends Title

case class SWEngineer(level: Level) extends Title

case class Employee(manager: Option[Employee], name: String, title: Title)

val employees = ???

val selfManagedLevel2Engineers = employees.collect {

case Employee(None, name, SWEngineer(Level2)) => name

}

Page 6: First-Class Patterns

Pattern Matching 101

Filter

Extract

Does it have the structure I want? [Yes/No]

If so, extract out the pieces that are relevant to me.

Page 7: First-Class Patterns

Pattern Matching 101-- take the first n elements from a list

take 0 _ = []take _ [] = []take n (x:xs) = x : take (n-1) xs

Filter - does it have non-empty list structure?

Extract - Give me ‘head’ and ‘tail’

Page 8: First-Class Patterns

Pattern Matching 101Products Sumscase class Employee(

manager: Option[Employee],

name: String,

title: Title

)

sealed trait Title

case object DBAdmin extends Title

case class SWEngineer(level: Level)

extends Title

class Account {

...

public Account(BigDecimal balance, User holder) {

...

}

}

interface Shape { }

class Rect extends Shape { … }

class Ellipse extends Shape { … }

class Pentagon extends Shape { … }

terms terms

Page 9: First-Class Patterns

First-Class-ness 101

data Maybe a = Nothing | Just a deriving (Eq, Ord) class Person {

public Person(String name, int age) {

...

}

}

Page 10: First-Class Patterns

First-Class-ness 101

Magic interferes with your ability to abstract and compose.

Page 11: First-Class Patterns

Magical PatternsOrdinary Duplicationsealed trait Provenanceobject Provenance { … case class Either(left: Provenance, right: Provenance) extends Provenance case class Both(left: Provenance, right: Provenance) extends Provenance

def allOf(xs: Seq[Provenance]): Provenance = { if (xs.length == 0) Unknown else if (xs.length == 1) xs.head else xs.reduce(Both.apply) }

def anyOf(xs: Seq[Provenance]): Provenance = { if (xs.length == 0) Unknown else if (xs.length == 1) xs.head else xs.reduce(Either.apply) }}

Page 12: First-Class Patterns

Magical PatternsFactoringsealed trait Provenanceobject Provenance { case object Unknown extends Provenance case object Value extends Provenance case class Relation(value: SqlRelation) extends Provenance case class Either(left: Provenance, right: Provenance) extends Provenance case class Both(left: Provenance, right: Provenance) extends Provenance

def allOf(xs: Seq[Provenance]): Provenance = reduce(xs)(Both.apply)

def anyOf(xs: Seq[Provenance]): Provenance = reduce(xs)(Either.apply)

private def reduce(xs: Seq[Provenance])(f: (Provenance, Provenance) => Provenance): Provenance = { if (xs.length == 0) Unknown else if (xs.length == 1) xs.head else xs.reduce(f) }}

Page 13: First-Class Patterns

Magical PatternsFactoringz sealed trait Provenance object Provenance { case object Unknown extends Provenance case object Value extends Provenance case class Relation(value: SqlRelation) extends Provenance case class Either(left: Provenance, right: Provenance) extends Provenance case class Both(left: Provenance, right: Provenance) extends Provenance

private def strict[A, B, C](f: (A, B) => C): (A, => B) => C = (a, b) => f(a, b)

val BothMonoid = Monoid.instance(strict[Provenance, Provenance, Provenance](Both.apply), Unknown) val EitherMonoid = Monoid.instance(strict[Provenance, Provenance, Provenance](Either.apply), Unknown)

def allOf(xs: List[Provenance]): Provenance = Foldable[List].foldMap(xs)(identity)(BothMonoid)

def anyOf(xs: List[Provenance]): Provenance = Foldable[List].foldMap(xs)(identity)(EitherMonoid) }

Page 14: First-Class Patterns

Magical PatternsToxic Duplication - Abstraction Fail val Add = Mapping("(+)", "Adds two numeric values", NumericDomain,

(partialTyper {

case Type.Const(Data.Number(v1)) :: v2 :: Nil if (v1.signum == 0) => v2

case v1 :: Type.Const(Data.Number(v2)) :: Nil if (v2.signum == 0) => v1

case Type.Const(Data.Int(v1)) :: Type.Const(Data.Int(v2)) :: Nil =>

Type.Const(Data.Int(v1 + v2))

case Type.Const(Data.Number(v1)) :: Type.Const(Data.Number(v2)) :: Nil =>

Type.Const(Data.Dec(v1 + v2))

}) ||| numericWidening

)

Page 15: First-Class Patterns

Magical PatternsPattern Combinators - Composition Fail

● Product & sum○ PartialFunction.orElse

● Negation● Defaults● Use-case specific

Page 16: First-Class Patterns

Magical Patterns

Magical patterns limit your ability to abstract over patterns and to

compose them together to create new patterns.

Page 17: First-Class Patterns

First-Class Patterns

First-class patterns are built using other language features so you can

abstract and compose them.

Page 18: First-Class Patterns

First-Class PatternsHaskell Exampleex4 :: Either (Int,Int) Int -> Int

ex4 a = match a $

(1+) <$> (left (pair var (cst 4)) ->> id

<|> right var ->> id)

<|> left (pair __ var) ->> id

http://hackage.haskell.org/package/first-class-patterns

Page 19: First-Class Patterns

First-Class Patterns“For the rest of us”

X match {

case Y => Z

}

type Pattern???

Page 20: First-Class Patterns

First-Class PatternsStructure

X match {

case Y => Z

}

type Pattern???

Input

Output

Extraction

Page 21: First-Class Patterns

First-Class PatternsOne ‘Option’

X match {

case Y => Z

}

type Pattern[X, Z] = X => Option[Z]

Input

Output

Extraction

Page 22: First-Class Patterns

First-Class PatternsBasic Patterns

def some[A, B](p: Pattern[A, B]): Pattern[Option[A], B] = _.flatMap(p)

def none[A, B]: Pattern[A, B] = Function.const( None)

def k[A](v0: A): Pattern[A, A] = v => if (v == v0) Some(v0) else None

def or[A, B](p1: Pattern[A, B], p2: Pattern[A, B]): Pattern[A, B] = v => p1(v).orElse(p2(v))

scala> or(none, some(k( 2)))(Some(2))res3: Option[Int] = Some(2)

https://gist.github.com/jdegoes/9240971

Page 23: First-Class Patterns

First-Class Patterns - JS

http://jsfiddle.net/AvL4V/

And Now in JavaScript….Because

Page 24: First-Class Patterns

First-Class PatternsLimitations

● Extractors cannot throw away information, leading to ‘dummy parameters’

● Negation not possible under any circumstances

● No partiality warnings or built-in catch all

Page 25: First-Class Patterns

Exercises1. Define a Pattern Combinator to Fix: val Add = Mapping("(+)", "Adds two numeric values", NumericDomain,

(partialTyper {

case Type.Const(Data.Number(v1)) :: v2 :: Nil if (v1.signum == 0) => v2

case v1 :: Type.Const(Data.Number(v2)) :: Nil if (v2.signum == 0) => v1

case Type.Const(Data.Int(v1)) :: Type.Const(Data.Int(v2)) :: Nil =>

Type.Const(Data.Int(v1 + v2))

case Type.Const(Data.Number(v1)) :: Type.Const(Data.Number(v2)) :: Nil =>

Type.Const(Data.Dec(v1 + v2))

}) ||| numericWidening

)

EASY

Page 26: First-Class Patterns

Exercises2. Define ‘Pattern’ and some core patterns in the language of your choice

EASY

Page 27: First-Class Patterns

Exercises3. Define an ‘And’ pattern that requires both inputs match

EASY

Page 28: First-Class Patterns

Exercises4. Define an alternate definition of pattern (along with a few core patterns) that permits pattern negation

4.b Optional: Use this to allow catch-alls

MODERATE

Page 29: First-Class Patterns

Exercises5. Define an alternate definition of pattern (along with a few core patterns) that permits extractors to throw away information

HARD

Page 30: First-Class Patterns

THANK YOUFirst-Class Patterns

John A. De Goes - @jdegoesFrontier Developers, February 26