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.

of

Mining Functional Patterns Slide 1 Mining Functional Patterns Slide 2 Mining Functional Patterns Slide 3 Mining Functional Patterns Slide 4 Mining Functional Patterns Slide 5 Mining Functional Patterns Slide 6 Mining Functional Patterns Slide 7 Mining Functional Patterns Slide 8 Mining Functional Patterns Slide 9 Mining Functional Patterns Slide 10 Mining Functional Patterns Slide 11 Mining Functional Patterns Slide 12 Mining Functional Patterns Slide 13 Mining Functional Patterns Slide 14 Mining Functional Patterns Slide 15 Mining Functional Patterns Slide 16 Mining Functional Patterns Slide 17 Mining Functional Patterns Slide 18 Mining Functional Patterns Slide 19 Mining Functional Patterns Slide 20 Mining Functional Patterns Slide 21 Mining Functional Patterns Slide 22 Mining Functional Patterns Slide 23 Mining Functional Patterns Slide 24 Mining Functional Patterns Slide 25 Mining Functional Patterns Slide 26 Mining Functional Patterns Slide 27 Mining Functional Patterns Slide 28 Mining Functional Patterns Slide 29 Mining Functional Patterns Slide 30 Mining Functional Patterns Slide 31 Mining Functional Patterns Slide 32 Mining Functional Patterns Slide 33 Mining Functional Patterns Slide 34 Mining Functional Patterns Slide 35 Mining Functional Patterns Slide 36 Mining Functional Patterns Slide 37 Mining Functional Patterns Slide 38 Mining Functional Patterns Slide 39 Mining Functional Patterns Slide 40 Mining Functional Patterns Slide 41 Mining Functional Patterns Slide 42 Mining Functional Patterns Slide 43 Mining Functional Patterns Slide 44 Mining Functional Patterns Slide 45 Mining Functional Patterns Slide 46 Mining Functional Patterns Slide 47 Mining Functional Patterns Slide 48 Mining Functional Patterns Slide 49
Upcoming SlideShare
An Algebraic Approach to Functional Domain Modeling
Next
Download to read offline and view in fullscreen.

4 Likes

Share

Download to read offline

Mining Functional Patterns

Download to read offline

This is going to be a discussion about design patterns. But I promise it’s going to be very different from the Gang of Four patterns that we all have used and loved in Java.

It doesn’t have any mathematics or category theory - it’s about developing an insight that lets u identify code structures that u think may be improved with a beautiful transformation of an algebraic pattern.

In earlier days of Java coding we used to feel proud when we could locate a piece of code that could be transformed into an abstract factory and the factory bean could be injected using Spring DI. The result was we ended up maintaining not only Java code, but quite a bit of XML too, untyped and unsafe. This was the DI pattern in full glory. In this session we will discuss patterns that don’t look like external artifacts, they are part of the language, they have some mathematical foundations in the sense that they have an algebra that actually compose and compose organically to evolve larger abstractions.

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

Mining Functional Patterns

  1. 1. Mining Functional Patterns Debasish Ghosh @debasishg
  2. 2. Plan today .. • Design Pattern • Algebra • Algebra <=> Functional Patterns • Mining patterns on real world Scala code
  3. 3. Code ahead .. Scala code .. .. though the principles apply equally well to any statically typed functional programming language ..
  4. 4. Solution to a Problem in Context Design Pattern
  5. 5. Solution to a Problem in Context Design Pattern
  6. 6. Solution to a Problem in Context Design Pattern
  7. 7. Solution to a Problem in Context Design Pattern
  8. 8. Solution to a Problem in Context Design Pattern we are given a problemgeneric component (invariant across context of application) context dependent (varies with the context of problem)
  9. 9. What is an Algebra ? Algebra is the study of algebraic structures In mathematics, and more specifically in abstract algebra, an algebraic structure is a set (called carrier set or underlying set) with one or more finitary operations defined on it that satisfies a list of axioms - Wikipedia (https://en.wikipedia.org/wiki/Algebraic_structure)
  10. 10. Set A ϕ : A × A → A for (a, b) ∈ A ϕ(a, b) a ϕ b given a binary operation for specific a, b or The Algebra of Sets
  11. 11. 3 + 2 = 5 7 + 4 = 11 2 + 0 = 2 0 + 6 = 6 8 + 9 = 9 + 8. Binary operation Identity operation Associative operation always produces an integer (closure of operations) One specific instance of the Algebra
  12. 12. Set A ϕ : A × A → A given a binary operation (a ϕ b) ϕ c = a ϕ (b ϕ c) associative for (a, b, c) ∈ A Let’s enhance the Algebra ..
  13. 13. Set A ϕ : A × A → A given a binary operation (a ϕ b) ϕ c = a ϕ (b ϕ c) associative for (a, b, c) ∈ A The Algebra of Semigroups
  14. 14. Set A ϕ : A × A → A given a binary operation (a ϕ b) ϕ c = a ϕ (b ϕ c) associative for (a, b, c) ∈ A a ϕ I = I ϕ a = a for (a, I ) ∈ A identity The Algebra of Monoids
  15. 15. Algebra <=> Protocol class Monoid a where mempty :: a mappend :: a -> a -> a
  16. 16. Algebra <=> Protocol with Laws class Monoid a where mempty :: a mappend :: a -> a -> a -- Identity laws x <> mempty = x mempty <> x = x -- Associativity (x <> y) <> z = x <> (y <> z)
  17. 17. Monoid In Scala trait Semigroup[A] { def combine(x: A, y: A): A } trait Monoid[A] extends Semigroup[A] { def empty: A }
  18. 18. Monoid In Scala trait Semigroup[A] { def combine(x: A, y: A): A } trait Monoid[A] extends Semigroup[A] { def empty: A } val intAdditionMonoid: Monoid[Int] = new Monoid[Int] { def empty: Int = 0 def combine(x: Int, y: Int): Int = x + y } • algebra (interface) • reusable • polymorphic • standard library code • instance (implementation) • specific for a datatype val moneyAdditionMonoid: Monoid[Money] = new Monoid[Money] { def empty: Money = ??? def combine(x: Money, y: Money): Money = ??? } • domain specific instance • specific for Money • application library code
  19. 19. Monoid In Scala trait Semigroup[A] { def combine(x: A, y: A): A } trait Monoid[A] extends Semigroup[A] { def empty: A } val intAdditionMonoid: Monoid[Int] = new Monoid[Int] { def empty: Int = 0 def combine(x: Int, y: Int): Int = x + y } • algebra (interface) • reusable • polymorphic • standard library code • instance (implementation) • specific for a datatype val moneyAdditionMonoid: Monoid[Money] = new Monoid[Money] { def empty: Money = ??? def combine(x: Money, y: Money): Money = ??? } • domain specific instance • specific for Money • application library code Generic Specific • Specific implementations use the generic protocol/interface • This reusability is enforced by parametricity (no type specific info in the protocol) • Genericity implies reusability
  20. 20. Monoid In Scala trait Semigroup[A] { def combine(x: A, y: A): A } trait Monoid[A] extends Semigroup[A] { def empty: A } val intAdditionMonoid: Monoid[Int] = new Monoid[Int] { def empty: Int = 0 def combine(x: Int, y: Int): Int = x + y } • algebra (interface) • reusable • polymorphic • standard library code • instance (implementation) • specific for a datatype val moneyAdditionMonoid: Monoid[Money] = new Monoid[Money] { def empty: Money = ??? def combine(x: Money, y: Money): Money = ??? } • domain specific instance • specific for Money • application library code Pattern Instances generic & reusable context specific
  21. 21. Functional Patterns • Generic, reusable algebra • Parametric on types • Clear separation between pattern (algebra) and its instances • Composable through function composition
  22. 22. Functional Patterns • Standard vocabulary - people know these terms, know these operations and their types • Rich ecosystem support through standard libraries • Functions defined in only terms of these interfaces / algebra can be reused by application level data types that follow the pattern
  23. 23. Functional Patterns - freebies • Given a type that has an instance of a Monoid, if we have a List of such objects, we can combine them for free using the combine function of Monoid.Also note that the behavior of combine is completely polymorphic - depends on the instance of Monoid that you pass in. • Given a typeV that has an instance of a Monoid, Map[K, V] also gets a Monoid. Part of standard library, but as an application developer you get this for free. • .. and there are many such examples ..
  24. 24. Functional Patterns - Multiplicative Power Money: Monoid Payment: Monoid Foo: Monoid Bar: Monoid Polymorphic behaviors in the library that expects a Monoid Domain Model Types with Monoid instances def fold[A](fa: F[A]) (implicit A: Monoid[A]): A = // .. def foldMap[A, B](fa: F[A])(f: A => B) (implicit B: Monoid[B]): B = // .. def foldMapM[G[_], A, B](fa: F[A]) (f: A => G[B])(implicit G: Monad[G], B: Monoid[B]): G[B] = // .. (multiply)
  25. 25. Domain Model // a sum type for Currency sealed trait Currency case object USD extends Currency case object AUD extends Currency case object JPY extends Currency case object INR extends Currency // a Money can have denominations in multiple // currencies class Money (val items: Map[Currency, BigDecimal]) { def toBaseCurrency: BigDecimal = items.foldLeft(BigDecimal(0)) { case (a, (ccy, amount)) => a + Money.exchangeRateWithUSD.get(ccy).getOrElse(BigDecimal(1)) * amount } def isDebit = toBaseCurrency < 0 }
  26. 26. Domain Model object Money { final val zeroMoney = new Money(Map.empty[Currency, BigDecimal]) // smart constructor def apply(amount: BigDecimal, ccy: Currency) = new Money(Map(ccy -> amount)) // concrete implementation: add two Money objects def add(m: Money, n: Money) = new Money( (m.items.toList ++ n.items.toList) .groupBy(_._1) .map { case (k, v) => (k, v.map(_._2).sum) } ) // sample implementation final val exchangeRateWithUSD: Map[Currency, BigDecimal] = Map(AUD -> 0.76, JPY -> 0.009, INR -> 0.016, USD -> 1.0) } concrete implementation of fusing 2 Maps - can we generalize it ?
  27. 27. Domain Model import java.time.OffsetDateTime // Account with a specific unique account no case class Account(no: String, name: String, openDate: OffsetDateTime, closeDate: Option[OffsetDateTime] = None) { override def equals(o: Any): Boolean = o match { case Account(`no`, _, _, _) => true case _ => false } override def hashCode() = no. ## } // Payment made for a particular Account case class Payment(account: Account, amount: Money, dateOfPayment: OffsetDateTime)
  28. 28. Domain Model import Money._ object Payments { def creditAmount(p: Payment): Money = if (p.amount.isDebit) zeroMoney else p.amount // concrete implementation def valuation(payments: List[Payment]): Money = payments.foldLeft(zeroMoney) { (a, e) => add(a, creditAmount(e)) } // concrete implementation that uses concrete methods of List def maxPayment(payments: List[Payment]): Money = payments.map(creditAmount).maxBy(_.toBaseCurrency) // adjust balances and payments def newBalances(currentBalances: Map[Account, Money], currentPayments: Map[Account, Money]): Map[Account, Money] = { // complicated logic that merges the 2 Maps Map.empty[Account, Money] } } complex implementation of fusing 2 Maps - can we generalize it ? 1. similar contract: Is there any commonality of behaviors that we can extract ? 2. both iterate over the collection and compute an aggregate
  29. 29. Domain Model object Money { final val zeroMoney = new Money(Map.empty[Currency, BigDecimal]) // smart constructor def apply(amount: BigDecimal, ccy: Currency) = new Money(Map(ccy -> amount)) // concrete implementation: add two Money objects def add(m: Money, n: Money) = new Money( (m.items.toList ++ n.items.toList) .groupBy(_._1) .map { case (k, v) => (k, v.map(_._2).sum) } ) // sample implementation final val exchangeRateWithUSD: Map[Currency, BigDecimal] = Map(AUD -> 0.76, JPY -> 0.009, INR -> 0.016, USD -> 1.0) } concrete implementation of fusing 2 Maps - can we generalize it ?
  30. 30. Domain Model object Money { final val zeroMoney = new Money(Map.empty[Currency, BigDecimal]) // smart constructor def apply(amount: BigDecimal, ccy: Currency) = new Money(Map(ccy -> amount)) // concrete implementation: add two Money objects def add(m: Money, n: Money) = new Money( (m.items.toList ++ n.items.toList) .groupBy(_._1) .map { case (k, v) => (k, v.map(_._2).sum) } ) // sample implementation final val exchangeRateWithUSD: Map[Currency, BigDecimal] = Map(AUD -> 0.76, JPY -> 0.009, INR -> 0.016, USD -> 1.0) } (a) adds 2 Money objects (b) has to be associative identity operation Pattern: Monoid for Money!
  31. 31. Domain Model import Money._ object Payments { def creditAmount(p: Payment): Money = if (p.amount.isDebit) zeroMoney else p.amount // concrete implementation def valuation(payments: List[Payment]): Money = payments.foldLeft(zeroMoney) { (a, e) => add(a, creditAmount(e)) } // concrete implementation that uses concrete methods of List def maxPayment(payments: List[Payment]): Money = payments.map(creditAmount).maxBy(_.toBaseCurrency) // adjust balances and payments def newBalances(currentBalances: Map[Account, Money], currentPayments: Map[Account, Money]): Map[Account, Money] = { // complicated logic that merges the 2 Maps Map.empty[Account, Money] } } complex implementation of fusing 2 Maps - can we generalize it ? 1. similar contract: Is there any commonality of behaviors that we can extract ? 2. both iterate over the collection and compute an aggregate
  32. 32. Domain Model import Money._ object Payments { def creditAmount(p: Payment): Money = if (p.amount.isDebit) zeroMoney else p.amount // concrete implementation def valuation(payments: List[Payment]): Money = payments.foldLeft(zeroMoney) { (a, e) => add(a, creditAmount(e)) } // concrete implementation that uses concrete methods of List def maxPayment(payments: List[Payment]): Money = payments.map(creditAmount).maxBy(_.toBaseCurrency) // adjust balances and payments def newBalances(currentBalances: Map[Account, Money], currentPayments: Map[Account, Money]): Map[Account, Money] = { // complicated logic that merges the 2 Maps Map.empty[Account, Money] } } 2 different ways to combine a bunch of Money instances (a) combine to add (b) combine to find the max You get a monoid for Map[K, V] if V has a monoid - part of standard library. (a) combine the 2 Maps Pattern: Monoid for Money!
  33. 33. Looking Back • Similar problem • Different context • Reuse of the same algebra • Different concrete instances of the algebra
  34. 34. import Money._ object Payments { def creditAmount(p: Payment): Money = if (p.amount.isDebit) zeroMoney else p.amount // concrete implementation def valuation(payments: List[Payment]): Money = payments.foldLeft(zeroMoney) { (a, e) => add(a, creditAmount(e)) } // concrete implementation that uses concrete methods of List def maxPayment(payments: List[Payment]): Money = payments.map(creditAmount).maxBy(_.toBaseCurrency) // adjust balances and payments def newBalances(currentBalances: Map[Account, Money], currentPayments: Map[Account, Money]): Map[Account, Money] = { // complicated logic that merges the 2 Maps Map.empty[Account, Money] } } Domain Model • An aggregation that produces a single result • Do we really need a List for the operation that we are doing ? • Use the least powerful abstraction that you need Pattern: Foldable
  35. 35. Abstracting over Structure & Operation trait Foldable[F[_]] { def foldleft[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 = foldleft(as, m.zero, (b: B, a: A) => m.combine(b, f(a))) } def mapReduce[F[_], A, B](as: F[A], f: A => B) (implicit ff: Foldable[F], m: Monoid[B]) = ff.foldMap(as, f)
  36. 36. Domain Model object Payments extends MoneyInstances with Utils { def creditAmount: Payment => Money = { p => if (p.amount.isDebit) zeroMoney else p.amount } def valuation(payments: List[Payment]): Money = { implicit val m: Monoid[Money] = MoneyAddMonoid mapReduce(payments)(creditAmount) } def maxPayment(payments: List[Payment]): Money = { implicit val m: Monoid[Money] = MoneyOrderMonoid mapReduce(payments)(creditAmount) } def newBalances(currentBalances: Map[Account, Money], currentPayments: Map[Account, Money]): Map[Account, Money] = { implicit val m = MoneyAddMonoid currentBalances |+| currentPayments } } • Completely generic implementation with Money manipulation logic moved to the library code • Reusability FTW
  37. 37. Parametricity def mapReduce[F[_], A, B](as: F[A], f: A => B) (implicit ff: Foldable[F], m: Monoid[B]) = ff.foldMap(as, f) • Parametric polymorphism • No dependence on concrete types • Reusable under multiple implementation context • Limited implementation possibilities by definition • Honors the principle of using the least powerful abstraction that works • The most important virtue of good functional patterns
  38. 38. Domain Model (Handling Domain Validations) private void validateState() throws ModelCtorException { ModelCtorException ex = new ModelCtorException(); if (FAILS == Check.optional(fId, Check.range(1,50))) { ex.add("Id is optional, 1 ..50 chars."); } if (FAILS == Check.required(fName, Check.range(2,50))) { ex.add("Restaurant Name is required, 2 ..50 chars."); } if (FAILS == Check.optional(fLocation, Check.range(2,50))) { ex.add("Location is optional, 2 ..50 chars."); } Validator[] priceChecks = {Check.range(ZERO, HUNDRED), Check.numDecimalsAlways(2)}; if (FAILS == Check.optional(fPrice, priceChecks)) { ex.add("Price is optional, 0.00 to 100.00."); } if (FAILS == Check.optional(fComment, Check.range(2,50))) { ex.add("Comment is optional, 2 ..50 chars."); } if ( ! ex.isEmpty() ) throw ex; }
  39. 39. Domain Model (Managing Configurations) public Handler getHandler(Config config) throws Exception { final String defaultTopic = config.getString("default_topic"); boolean propagate = false; try { propagate = config.getBoolean("propagate"); } catch (ConfigException.Missing ignored) { } if ("null".equals(defaultTopic)) { log.warn("default topic is "null"; messages will be discarded unless tagged with kt:"); } final Properties properties = new Properties(); for (Map.Entry<String, ConfigValue> kv : config.getConfig("producer_config").entrySet()) { properties.put(kv.getKey(), kv.getValue().unwrapped().toString()); } final String clientId = // .. // .. EncryptionConfig encryptionConfig = new EncryptionConfig(); try { Config encryption = config.getConfig("encryption"); encryptionConfig.encryptionKey = encryption.getString("key"); encryptionConfig.encryptionAlgorithm = encryption.getString("algorithm"); encryptionConfig.encryptionTransformation = encryption.getString("transformation"); encryptionConfig.encryptionProvider = encryption.getString("provider"); } catch (ConfigException.Missing ignored) { encryptionConfig = null; } return new KafkaHandler(clientId, propagate, defaultTopic, producer, encryptionConfig); }
  40. 40. Antipatterns • Repetition • Imperative, not expression based - hence not composable • Littered with exception handling code (try/catch) - violates referential transparency • Not modular
  41. 41. Domain Model type ErrorOr[A] = Either[Exception, A] private def readString(path: String, config: Config): ErrorOr[String] = try { Either.right(config.getString(path)) } catch { case ex: Exception => Either.left(ex) } Step 1: Abstract exceptions with an algebra. We need to handle exceptions, but not throw it upstream case class KafkaSettings( brokers: String, zk: String, fromTopic: String, toTopic: String, errorTopic: String ) Step 2: Use an algebraic data type for configuration information
  42. 42. Domain Model type ErrorOr[A] = Either[Exception, A] private def readString(path: String, config: Config): ErrorOr[String] = try { Either.right(config.getString(path)) } catch { case ex: Exception => Either.left(ex) } import com.typesafe.config.Config def fromKafkaConfig(config: Config) = for { b <- readString("dcos.kafka.brokers", config) z <- readString("dcos.kafka.zookeeper", config) f <- readString("dcos.kafka.fromtopic", config) t <- readString("dcos.kafka.totopic", config) e <- readString("dcos.kafka.errortopic", config) } yield KafkaSettings(b, z, f, t, e) • Algebra of Monad for composition of readString • Looks imperative (and hence intuitive) though in reality it’s an expression • Reusing algebra and defining implementation specific context
  43. 43. Domain Model import com.typesafe.config.Config def fromKafkaConfig(config: Config): ErrorOr[KafkaSettings] = for { b <- readString("dcos.kafka.brokers", config) z <- readString("dcos.kafka.zookeeper", config) f <- readString("dcos.kafka.fromtopic", config) t <- readString("dcos.kafka.totopic", config) e <- readString("dcos.kafka.errortopic", config) } yield KafkaSettings(b, z, f, t, e) Repetitions ..
  44. 44. Algebraic Composition def fromKafkaConfig(config: Config): ErrorOr[KafkaSettings] = for { b <- readString("dcos.kafka.brokers", config) z <- readString("dcos.kafka.zookeeper", config) f <- readString("dcos.kafka.fromtopic", config) t <- readString("dcos.kafka.totopic", config) e <- readString("dcos.kafka.errortopic", config) } yield KafkaSettings(b, z, f, t, e) Either[Exception, KafkaSettings]
  45. 45. Algebraic Composition Either[Exception, KafkaSettings]Config => def fromKafkaConfig: Config => ErrorOr[KafkaSettings] = (config: Config) => for { b <- readString("dcos.kafka.brokers", config) z <- readString("dcos.kafka.zookeeper", config) f <- readString("dcos.kafka.fromtopic", config) t <- readString("dcos.kafka.totopic", config) e <- readString("dcos.kafka.errortopic", config) } yield KafkaSettings(b, z, f, t, e) ReaderT[ErrorOr, Config, KafkaSettings] we want a better abstraction for reading stuff in the abstraction needs to compose with Either
  46. 46. Algebraic Composition Either[Exception, KafkaSettings]Config => def fromKafkaConfig: Config => ErrorOr[KafkaSettings] = (config: Config) => for { b <- readString("dcos.kafka.brokers", config) z <- readString("dcos.kafka.zookeeper", config) f <- readString("dcos.kafka.fromtopic", config) t <- readString("dcos.kafka.totopic", config) e <- readString("dcos.kafka.errortopic", config) } yield KafkaSettings(b, z, f, t, e) ReaderT[ErrorOr, Config, KafkaSettings] • An algebra abstracting our earlier expression • Does 2 things - the Reader monad wraps a unary function & the T part indicating a monad transformer composes the 2 monads, the Reader and the Either • ReaderT is also a monad
  47. 47. Algebraic Composition type ConfigReader[A] = ReaderT[ErrorOr, Config, A] def fromKafkaConfig: ConfigReader[KafkaSettings] = for { b <- readString("dcos.kafka.brokers") z <- readString("dcos.kafka.zookeeper") f <- readString("dcos.kafka.fromtopic") t <- readString("dcos.kafka.totopic") e <- readString("dcos.kafka.errortopic") } yield KafkaSettings(b, z, f, t, e) def readString(path: String): ConfigReader[String] = Kleisli { (config: Config) => try { Either.right(config.getString(path)) } catch { case ex: Exception => Either.left(ex) } }
  48. 48. Functional Patterns • Reuse of already existing algebra (Reader, Monad, Either etc.) • Algebraic composition - form larger patterns from smaller ones • Abstraction remains composable • And modular
  49. 49. Thanks!
  • carlospavanetti

    Aug. 5, 2019
  • JanGrybo1

    Aug. 20, 2018
  • FarleyCaesar

    Nov. 20, 2017
  • PhilDerome

    Nov. 19, 2017

This is going to be a discussion about design patterns. But I promise it’s going to be very different from the Gang of Four patterns that we all have used and loved in Java. It doesn’t have any mathematics or category theory - it’s about developing an insight that lets u identify code structures that u think may be improved with a beautiful transformation of an algebraic pattern. In earlier days of Java coding we used to feel proud when we could locate a piece of code that could be transformed into an abstract factory and the factory bean could be injected using Spring DI. The result was we ended up maintaining not only Java code, but quite a bit of XML too, untyped and unsafe. This was the DI pattern in full glory. In this session we will discuss patterns that don’t look like external artifacts, they are part of the language, they have some mathematical foundations in the sense that they have an algebra that actually compose and compose organically to evolve larger abstractions.

Views

Total views

1,568

On Slideshare

0

From embeds

0

Number of embeds

82

Actions

Downloads

29

Shares

0

Comments

0

Likes

4

×