Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Upcoming SlideShare
What to Upload to SlideShare
Next
Download to read offline and view in fullscreen.

0

Share

Power of functions in a typed world

Download to read offline

A tutorial on FP in a statically typed language on the JVM using Scala. Targeted mostly towards beginners. Presented at Functional Conf 2018.

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all
  • Be the first to like this

Power of functions in a typed world

  1. 1. Power of Functions in a Typed World - A JVM Perspective Debasish Ghosh (dghosh@acm.org) @debasishg
  2. 2. functions as the primary abstraction of design
  3. 3. pure functions as the primary abstraction of design
  4. 4. statically typed pure functions as the primary abstraction of design def foo[A, B](arg: A): B
  5. 5. Motivation • Explore if we can consider functions as the primary abstraction of design • Function composition to build larger behaviors out of smaller ones • How referential transparency helps composition and local reasoning • Types as the substrate (algebra) of composition
  6. 6. Types Functions (Mapping between types) (Algebraic) • sum type • product type • ADTs • compose • based on algebra of types Modules (Grouping related functions) • compose • can be parameterized Application (Top level artifact)
  7. 7. Functional Programming
  8. 8. Programming with Functions f A B
  9. 9. def plus2(arg: Int): Int = arg + 2
  10. 10. def size(n: Int): Int = 1000 / n
  11. 11. def size(n: Int): Option[Int] = if (n == 0) None else Some(1000 / n) Pure Value
  12. 12. Functional Programming • Programming with pure functions • Output determined completely by the input • No side-effects • Referentially transparent
  13. 13. Referential Transparency
  14. 14. Referential Transparency • An expression is said to be referentially transparent if it can be replaced with its value without changing the behavior of a program • The value of an expression depends only on the values of its constituent expressions (if any) and these subexpressions may be replaced freely by others possessing the same value
  15. 15. Referential Transparency expression oriented programming substitution model of evaluation • An expression is said to be referentially transparent if it can be replaced with its value without changing the behavior of a program • The value of an expression depends only on the values of its constituent expressions (if any) and these subexpressions may be replaced freely by others possessing the same value
  16. 16. scala> val str = "Hello".reverse str: String = olleH scala> (str, str) res0: (String, String) = (olleH,olleH) scala> ("Hello".reverse, "Hello".reverse) res1: (String, String) = (olleH,olleH)
  17. 17. scala> def foo(iter: Iterator[Int]) = iter.next + 2 foo: (iter: Iterator[Int])Int scala> (1 to 20).iterator res0: Iterator[Int] = non-empty iterator scala> val a = foo(res0) a: Int = 3 scala> (a, a) res1: (Int, Int) = (3,3) scala> (1 to 20).iterator res3: Iterator[Int] = non-empty iterator scala> (foo(res3), foo(res3)) res4: (Int, Int) = (3,4)
  18. 18. def foo(i: Iterator[Int]): Int = { val a = i.next val b = a + 1 a + b } scala> val i = (1 to 20).iterator i: Iterator[Int] = non-empty iterator scala> foo(i) res8: Int = 3 scala> val i = (1 to 20).iterator i: Iterator[Int] = non-empty iterator scala> i.next + i.next + 1 res9: Int = 4 • non compositional • hinders local understanding • breaks RT
  19. 19. Referential Transparency enables Local Reasoning
  20. 20. and Function Composition
  21. 21. Types
  22. 22. What is meant by the algebra of a type ? •Nothing •Unit •Boolean •Byte •String
  23. 23. What is meant by the algebra of a type ? •Nothing -> 0 •Unit -> 1 •Boolean -> 2 •Byte -> 256 •String -> a lot
  24. 24. What is meant by the algebra of a type ? •(Boolean, Unit) •(Byte, Unit) •(Byte, Boolean) •(Byte, Byte) •(String, String)
  25. 25. What is meant by the algebra of a type ? •(Boolean, Unit) -> 2x1 = 2 •(Byte, Unit) -> 256x1 = 256 •(Byte, Boolean) -> 256x2 = 512 •(Byte, Byte) -> 256x256 = 65536 •(String, String) -> a lot
  26. 26. What is meant by the algebra of a type ? • Quiz: Generically, how many inhabitants can we have for a type (a, b)? • Answer: 1 inhabitant for each combination of a’s and b’s (a x b)
  27. 27. Product Types • Ordered pairs of values one from each type in the order specified - this and that • Can be generalized to a finite product indexed by a finite set of indices
  28. 28. Product Types in Scala type Point = (Int, Int) val p = (10, 12) case class Account(no: String, name: String, address: String, dateOfOpening: Date, dateOfClosing: Option[Date] )
  29. 29. What is meant by the algebra of a type ? •Boolean or Unit •Byte or Unit •Byte or Boolean •Byte or Byte •String or String
  30. 30. What is meant by the algebra of a type ? •Boolean or Unit -> 2+1 = 3 •Byte or Unit -> 256+1 = 257 •Byte or Boolean -> 256+2 = 258 •Byte or Byte -> 256+256 = 512 •String or String -> a lot
  31. 31. Sum Types • Model data structures involving alternatives - this or that • A tree can have a leaf or an internal node which, is again a tree • In Scala, a sum type is usually referred to as an Algebraic DataType (ADT)
  32. 32. Sum Types in Scala sealed trait Shape case class Circle(origin: Point, radius: BigDecimal) extends Shape case class Rectangle(diag_1: Point, diag_2: Point) extends Shape
  33. 33. Sum Types are Expressive • Booleans - true or false • Enumerations - sum types may be used to define finite enumeration types, whose values are one of an explicitly specified finite set • Optionality - the Option data type in Scala is encoded using a sum type • Disjunction - this or that, the Either data type in Scala • Failure encoding - the Try data type in Scala to indicate that the computation may raise an exception
  34. 34. sealed trait InstrumentType case object CCY extends InstrumentType case object EQ extends InstrumentType case object FI extends InstrumentType sealed trait Instrument { def instrumentType: InstrumentType } case class Equity(isin: String, name: String, issueDate: Date, faceValue: Amount) extends Instrument { final val instrumentType = EQ } case class FixedIncome(isin: String, name: String, issueDate: Date, maturityDate: Option[Date], nominal: Amount) extends Instrument { final val instrumentType = FI } case class Currency(isin: String) extends Instrument { final val instrumentType = CCY }
  35. 35. De-structuring with Pattern Matching def process(i: Instrument) = i match { case Equity(isin, _, _, faceValue) => // .. case FixedIncome(isin, _, issueDate, _, nominal) => // .. case Currency(isin) => // .. }
  36. 36. Exhaustiveness Check
  37. 37. Types
  38. 38. Algebra of Types def f: A => B = // .. def g: B => C = // ..
  39. 39. Algebraic Composition of Types def f: A => B = // .. def g: B => C = // .. def h: A => C = g compose f
  40. 40. scala> case class Employee(id: String, name: String, age: Int) defined class Employee scala> def getEmployee: String => Employee = id => Employee("a", "abc") getEmployee: String => Employee scala> def getSalary: Employee => BigDecimal = e => BigDecimal(100) getSalary: Employee => BigDecimal scala> def computeTax: BigDecimal => BigDecimal = s => 0.3 * s computeTax: BigDecimal => BigDecimal scala> getEmployee andThen getSalary andThen computeTax res3: String => BigDecimal = scala.Function1$$Lambda$1306/913564177@4ac77269 scala> res3("emp-100") res4: BigDecimal = 30.0
  41. 41. Function Composition • Functions compose when types align • And we get larger functions • Still values - composition IS NOT execution (composed function is still a value) • Pure and referentially transparent
  42. 42. Function Composition f:A => B g:B => C (f andThen g):A => C id[A]:A => A id[B]:B => B (Slide adapted from a presentation by Rob Norris at Scale by the Bay 2017)
  43. 43. Functions are first class
  44. 44. scala> List(1, 2, 3).map(e => e * 2) res0: List[Int] = List(2, 4, 6) scala> (1 to 10).filter(e => e % 2 == 0) res2: s.c.i.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10)
  45. 45. scala> def even(i: Int) = i % 2 == 0 even: (i: Int)Boolean scala> (1 to 10).filter(even) res3: s.c.i.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10) scala> (1 to 10).foldLeft(0)((a, e) => a + e) res4: Int = 55
  46. 46. Programming with Values
  47. 47. def size(n: Int): Option[Int] = if (n == 0) None else Some(1000 / n) Pure Value
  48. 48. def size(n: Int): Either[String,Int] = if (n == 0) Left(“Division by zero”) else Right(1000 / n) Pure Value
  49. 49. def size(n: Int): Either[String,Int] = if (n == 0) Left(“Division by zero”) else Right(1000 / n) Sum Type Error Path Happy Path
  50. 50. Types don’t lie unless you try and subvert your type system some languages like Haskell make subversion difficult
  51. 51. Types Functionsmapping between types closed under composition (algebraic) (referentially transparent)
  52. 52. Does this approach scale ?
  53. 53. Types Functions (Mapping between types) (Algebraic) • sum type • product type • ADTs • compose • based on algebra of types Modules (Grouping related functions) • compose • can be parameterized Application (Top level artifact)
  54. 54. trait Combiner[A] { def zero: A def combine(l: A, r: => A): A } //identity combine(x, zero) = combine(zero, x) = x // associativity combine(x, combine(y, z)) = combine(combine(x, y), z)
  55. 55. Module with an algebra trait Monoid[A] { def zero: A def combine(l: A, r: => A): A } //identity combine(x, zero) = combine(zero, x) = x // associativity combine(x, combine(y, z)) = combine(combine(x, y), z)
  56. 56. Module with an Algebra trait Foldable[F[_]] { def foldl[A, B](as: F[A], z: B, f: (B, A) => B): B def foldMap[A, B](as: F[A], f: A => B) (implicit m: Monoid[B]): B = foldl(as, m.zero, (b: B, a: A) => m.combine(b, f(a)) ) }
  57. 57. def mapReduce[F[_], A, B](as: F[A], f: A => B) (implicit ff: Foldable[F], m: Monoid[B]) = ff.foldMap(as, f) https://stackoverflow.com/a/4765918
  58. 58. case class Sum(value: Int) case class Product(value: Int) implicit val sumMonoid = new Monoid[Sum] { def zero = Sum(0) def add(a: Sum, b: Sum) = Sum(a.value + b.value) } implicit val productMonoid = new Monoid[Product] { def zero = Product(1) def add(a: Product, b: Product) = Product(a.value * b.value) }
  59. 59. val sumOf123 = mapReduce(List(1,2,3), Sum) val productOf456 = mapReduce(List(4,5,6), Product)
  60. 60. def mapReduce[F[_], A, B](as: F[A], f: A => B) (implicit ff: Foldable[F], m: Monoid[B]) = ff.foldMap(as, f)
  61. 61. def mapReduce[F[_], A, B](as: F[A], f: A => B) (implicit ff: Foldable[F], m: Monoid[B]) = ff.foldMap(as, f) combinator (higher order function)
  62. 62. def mapReduce[F[_], A, B](as: F[A], f: A => B) (implicit ff: Foldable[F], m: Monoid[B]) = ff.foldMap(as, f) Type constructor
  63. 63. def mapReduce[F[_], A, B](as: F[A], f: A => B) (implicit ff: Foldable[F], m: Monoid[B]) = ff.foldMap(as, f) F needs to honor the algebra of a Foldable
  64. 64. def mapReduce[F[_], A, B](as: F[A], f: A => B) (implicit ff: Foldable[F], m: Monoid[B]) = ff.foldMap(as, f) B needs to honor the algebra of a Monoid
  65. 65. def mapReduce[F[_], A, B](as: F[A], f: A => B) (implicit ff: Foldable[F], m: Monoid[B]) = ff.foldMap(as, f) combinator (higher order function) Type constructor F needs to honor the algebra of a Foldable B needs to honor the algebra of a Monoid
  66. 66. Types Functions (Mapping between types) (Algebraic) • sum type • product type • ADTs • compose • based on algebra of types Modules (Grouping related functions) • compose • can be parameterized Application (Top level artifact)
  67. 67. Types and Functions to drive our design instead of classes as in OO
  68. 68. Client places order - flexible format 1
  69. 69. Client places order - flexible format Transform to internal domain model entity and place for execution 1 2
  70. 70. Client places order - flexible format Transform to internal domain model entity and place for execution Trade & Allocate to client accounts 1 2 3
  71. 71. def clientOrders: List[ClientOrder] => List[Order]
  72. 72. def clientOrders: List[ClientOrder] => Either[Error, List[Order]]
  73. 73. def clientOrders: List[ClientOrder] => Either[Error, List[Order]] def execute(market: Market, brokerAccount: Account): List[Order] => Either[Error, List[Execution]] def allocate(accounts: List[Account]): List[Execution] => Either[Error, List[Trade]]
  74. 74. Function Composition f:A => B g:B => C (f andThen g):A => C id[A]:A => A id[B]:B => B (Slide adapted from a presentation by Rob Norris at Scale by the Bay 2017)
  75. 75. def clientOrders: List[ClientOrder] => Either[Error, List[Order]] def execute(market: Market, brokerAccount: Account): List[Order] => Either[Error, List[Execution]] def allocate(accounts: List[Account]): List[Execution] => Either[Error, List[Trade]]
  76. 76. Plain function composition doesn’t work here
  77. 77. We have those pesky Either[Error, ..] floating around
  78. 78. Effects
  79. 79. Effectful function composition
  80. 80. Effectful Function Composition f:A => F[B] g:B => F[C] (f andThen g):A => F[C] pure[A]:A => F[A] pure[B]:B => F[B] (Slide adapted from a presentation by Rob Norris at Scale by the Bay 2017)
  81. 81. Effectful Function Composition final case class Kleisli[F[_], A, B](run: A => F[B]) { def andThen[C](f: B => F[C]) : Kleisli[F, A, C] = // .. }
  82. 82. type ErrorOr[A] = Either[Error, A] def clientOrders: Kleisli[ErrorOr, List[ClientOrder], List[ClientOrder]] def execute(market: Market, brokerAccount: Account): Kleisli[ErrorOr, List[Order], List[Execution]] def allocate(accounts: List[Account]): Kleisli[ErrorOr, List[Execution], List[Trade]]
  83. 83. def tradeGeneration( market: Market, broker: Account, clientAccounts: List[Account]) = { clientOrders andThen execute(market, broker) andThen allocate(clientAccounts) }
  84. 84. type ErrorOr[A] = Either[Error, A] def clientOrders: Kleisli[ErrorOr, List[ClientOrder], List[ClientOrder]] def execute(market: Market, brokerAccount: Account): Kleisli[ErrorOr, List[Order], List[Execution]] def allocate(accounts: List[Account]): Kleisli[ErrorOr, List[Execution], List[Trade]] def tradeGeneration(market: Market, broker: Account, clientAccounts: List[Account]) = { clientOrders andThen execute(market, broker) andThen allocate(clientAccounts) }
  85. 85. type ErrorOr[A] = Either[Error, A] def clientOrders: Kleisli[ErrorOr, List[ClientOrder], List[ClientOrder]] def execute(market: Market, brokerAccount: Account): Kleisli[ErrorOr, List[Order], List[Execution]] def allocate(accounts: List[Account]): Kleisli[ErrorOr, List[Execution], List[Trade]] def tradeGeneration(market: Market, broker: Account, clientAccounts: List[Account]) = { clientOrders andThen execute(market, broker) andThen allocate(clientAccounts) } trait TradingService { }
  86. 86. type ErrorOr[A] = Either[Error, A] def clientOrders: Kleisli[ErrorOr, List[ClientOrder], List[ClientOrder]] def execute(market: Market, brokerAccount: Account): Kleisli[ErrorOr, List[Order], List[Execution]] def allocate(accounts: List[Account]): Kleisli[ErrorOr, List[Execution], List[Trade]] def tradeGeneration(market: Market, broker: Account, clientAccounts: List[Account]) = { clientOrders andThen execute(market, broker) andThen allocate(clientAccounts) } trait TradingService { }
  87. 87. trait TradingApplication extends TradingService with ReferenceDataService with ReportingService with AuditingService { // .. } object TradingApplication extends TradingApplication Modules Compose https://github.com/debasishg/fp-fnconf-2018
  88. 88. Thanks!

A tutorial on FP in a statically typed language on the JVM using Scala. Targeted mostly towards beginners. Presented at Functional Conf 2018.

Views

Total views

855

On Slideshare

0

From embeds

0

Number of embeds

43

Actions

Downloads

10

Shares

0

Comments

0

Likes

0

×