first-class patterns
Post on 22-Apr-2015
929 Views
Preview:
DESCRIPTION
TRANSCRIPT
First-Class PatternsJohn A. De Goes - @jdegoes
Frontier Developers, February 26
Agenda● Intro● Pattern Matching 101● First-Class-ness 101● Magical Patterns● First-Class Patterns● Exercises
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)
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
...
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
}
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.
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 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
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 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) }}
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) }}
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) }
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
)
Magical PatternsPattern 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 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
First-Class Patterns“For the rest of us”
X match {
case Y => Z
}
type Pattern???
First-Class PatternsStructure
X match {
case Y => Z
}
type Pattern???
Input
Output
Extraction
First-Class PatternsOne ‘Option’
X match {
case Y => Z
}
type Pattern[X, Z] = X => Option[Z]
Input
Output
Extraction
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
First-Class Patterns - JS
http://jsfiddle.net/AvL4V/
And Now in JavaScript….Because
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
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
Exercises2. Define ‘Pattern’ and some core patterns in the language of your choice
EASY
Exercises3. Define an ‘And’ pattern that requires both inputs match
EASY
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
Exercises5. Define an alternate definition of pattern (along with a few core patterns) that permits extractors to throw away information
HARD
THANK YOUFirst-Class Patterns
John A. De Goes - @jdegoesFrontier Developers, February 26
top related