First-Class Patterns
John A. De Goes - @jdegoes
Frontier Developers, February 26
Agenda
●
●
●
●
●
●

Intro
Pattern Matching 101
First-Class-ness 101
Magical Patterns
First-Class Patterns
Exercises
Intro
Pattern 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)
Intro - Examples
-- sign of a number
sign x |
|
|

x > 0
x == 0
x < 0

=
=
=

1
0
-1

-- take the first n elements from a list
take
take
take

0
_
n

_
[]
(x:xs)

=
=
=

[]
[]
x : take (n-1) xs

-- generate some javascript
valueToJs
valueToJs
valueToJs
valueToJs
...

:: Options -> ModuleName -> Environment -> Value -> JS
_ _ _ (NumericLiteral n) = JSNumericLiteral n
_ _ _ (StringLiteral s) = JSStringLiteral s
_ _ _ (BooleanLiteral b) = JSBooleanLiteral b
Intro - Examples
sealed 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
}
Pattern Matching 101
Filter
Does it have the structure I
want? [Yes/No]
If so, extract out the pieces
that are relevant to me.

Extract
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’
Pattern Matching 101
Products

Sums

case class Employee(

sealed trait Title

manager: Option[Employee],
name:

String,

title:

case object DBAdmin extends Title

terms

Title

case class SWEngineer(level: Level)
extends Title

)
class Account {

interface Shape { }

...

class Rect extends Shape { … }

public Account(BigDecimal balance, User holder) {

class Ellipse extends Shape { … }

...
}
}

class Pentagon extends Shape { … }

terms
First-Class-ness 101
data Maybe a = Nothing | Just a deriving (Eq, Ord)

class Person {
public Person(String name, int age) {
...
}
}
First-Class-ness 101

Magic interferes with your ability to
abstract and compose.
Magical Patterns
Ordinary Duplication
sealed
object
…
case
case

trait Provenance
Provenance {
class Either(left: Provenance, right: Provenance) extends Provenance
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)
}
}
Magical Patterns
Factoring
sealed
object
case
case
case
case
case

trait Provenance
Provenance {
object Unknown extends Provenance
object Value extends Provenance
class Relation(value: SqlRelation) extends Provenance
class Either(left: Provenance, right: Provenance) extends Provenance
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)
}
}
Magical Patterns
Factoringz
sealed
object
case
case
case
case
case

trait Provenance
Provenance {
object Unknown extends Provenance
object Value extends Provenance
class Relation(value: SqlRelation) extends Provenance
class Either(left: Provenance, right: Provenance) extends Provenance
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)
}
Magical Patterns
Toxic 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
)
Magical Patterns
Pattern Combinators - Composition Fail
● Product & sum
○ PartialFunction.orElse

● Negation
● Defaults
● Use-case specific
Magical Patterns
Magical patterns limit your ability
to abstract over patterns and to
compose them together to create new
patterns.
First-Class Patterns
First-class patterns are built using
other language features so you can
abstract and compose them.
First-Class Patterns
Haskell Example
ex4 :: 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
First-Class Patterns
“For the rest of us”
X match {
case Y => Z
}

type Pattern???
First-Class Patterns
Structure
Input

X match {
Output

case Y => Z
}

type Pattern???

Extraction
First-Class Patterns
One ‘Option’
Input

X match {
Output

case Y => Z
}

Extraction

type Pattern[X, Z] = X => Option[Z]
First-Class Patterns
Basic 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
First-Class Patterns - JS
And Now in JavaScript….Because

http://jsfiddle.net/AvL4V/
First-Class Patterns
Limitations
● Extractors cannot throw away information,
leading to ‘dummy parameters’
● Negation not possible under any
circumstances
● No partiality warnings or built-in catch all
Exercises
1. 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
Exercises
2. Define ‘Pattern’ and some core
patterns in the language of your choice

EASY
Exercises
3. Define an ‘And’ pattern that requires
both inputs match

EASY
Exercises
4. 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
Exercises
5. Define an alternate definition of
pattern (along with a few core patterns)
that permits extractors to throw away
information

HARD
THANK YOU
First-Class Patterns
John A. De Goes - @jdegoes
Frontier Developers, February 26

First-Class Patterns

  • 1.
    First-Class Patterns John A.De Goes - @jdegoes Frontier Developers, February 26
  • 2.
    Agenda ● ● ● ● ● ● Intro Pattern Matching 101 First-Class-ness101 Magical Patterns First-Class Patterns Exercises
  • 3.
    Intro Pattern Matching ● Dividesa (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)
  • 4.
    Intro - Examples --sign of a number sign x | | | x > 0 x == 0 x < 0 = = = 1 0 -1 -- take the first n elements from a list take take take 0 _ n _ [] (x:xs) = = = [] [] x : take (n-1) xs -- generate some javascript valueToJs valueToJs valueToJs valueToJs ... :: Options -> ModuleName -> Environment -> Value -> JS _ _ _ (NumericLiteral n) = JSNumericLiteral n _ _ _ (StringLiteral s) = JSStringLiteral s _ _ _ (BooleanLiteral b) = JSBooleanLiteral b
  • 5.
    Intro - Examples sealedtrait 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 }
  • 6.
    Pattern Matching 101 Filter Doesit have the structure I want? [Yes/No] If so, extract out the pieces that are relevant to me. Extract
  • 7.
    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’
  • 8.
    Pattern Matching 101 Products Sums caseclass Employee( sealed trait Title manager: Option[Employee], name: String, title: case object DBAdmin extends Title terms Title case class SWEngineer(level: Level) extends Title ) class Account { interface Shape { } ... class Rect extends Shape { … } public Account(BigDecimal balance, User holder) { class Ellipse extends Shape { … } ... } } class Pentagon extends Shape { … } terms
  • 9.
    First-Class-ness 101 data Maybea = Nothing | Just a deriving (Eq, Ord) class Person { public Person(String name, int age) { ... } }
  • 10.
    First-Class-ness 101 Magic interfereswith your ability to abstract and compose.
  • 11.
    Magical Patterns Ordinary Duplication sealed object … case case traitProvenance Provenance { class Either(left: Provenance, right: Provenance) extends Provenance 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) } }
  • 12.
    Magical Patterns Factoring sealed object case case case case case trait Provenance Provenance{ object Unknown extends Provenance object Value extends Provenance class Relation(value: SqlRelation) extends Provenance class Either(left: Provenance, right: Provenance) extends Provenance 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) } }
  • 13.
    Magical Patterns Factoringz sealed object case case case case case trait Provenance Provenance{ object Unknown extends Provenance object Value extends Provenance class Relation(value: SqlRelation) extends Provenance class Either(left: Provenance, right: Provenance) extends Provenance 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) }
  • 14.
    Magical Patterns Toxic 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 )
  • 15.
    Magical Patterns Pattern Combinators- Composition Fail ● Product & sum ○ PartialFunction.orElse ● Negation ● Defaults ● Use-case specific
  • 16.
    Magical Patterns Magical patternslimit your ability to abstract over patterns and to compose them together to create new patterns.
  • 17.
    First-Class Patterns First-class patternsare built using other language features so you can abstract and compose them.
  • 18.
    First-Class Patterns Haskell Example ex4:: 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
  • 19.
    First-Class Patterns “For therest of us” X match { case Y => Z } type Pattern???
  • 20.
    First-Class Patterns Structure Input X match{ Output case Y => Z } type Pattern??? Extraction
  • 21.
    First-Class Patterns One ‘Option’ Input Xmatch { Output case Y => Z } Extraction type Pattern[X, Z] = X => Option[Z]
  • 22.
    First-Class Patterns Basic Patterns defsome[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
  • 23.
    First-Class Patterns -JS And Now in JavaScript….Because http://jsfiddle.net/AvL4V/
  • 24.
    First-Class Patterns Limitations ● Extractorscannot throw away information, leading to ‘dummy parameters’ ● Negation not possible under any circumstances ● No partiality warnings or built-in catch all
  • 25.
    Exercises 1. Define aPattern 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
  • 26.
    Exercises 2. Define ‘Pattern’and some core patterns in the language of your choice EASY
  • 27.
    Exercises 3. Define an‘And’ pattern that requires both inputs match EASY
  • 28.
    Exercises 4. Define analternate definition of pattern (along with a few core patterns) that permits pattern negation 4.b Optional: Use this to allow catch-alls MODERATE
  • 29.
    Exercises 5. Define analternate definition of pattern (along with a few core patterns) that permits extractors to throw away information HARD
  • 30.
    THANK YOU First-Class Patterns JohnA. De Goes - @jdegoes Frontier Developers, February 26