Intro

  • CREATE ENTITY (today)
  • MODIFY ENTITY
    • LENSES FOR COMPLEX STRUCTURES
  • REPOSITORIES

Separate Business Domain from Effects

FILE:

jug.lodz.workshops.modeling.creation.SeparateDomainFromEffects

Type Aliases

During demos and exercises we will very often see Type aliases

type Input = Int
type Money = BigDecimal
type Message = String

thanks to type aliases we can add more meaningful names to primitive types so that we better understand context of its use.

Pure Business Functions

Let's assume that pure business functions

  • operate on pure domain types within given context like Money,Account (or Int in mathematical domain) etc.
  • for every input there is a valid output
val createMoney : Input => Money = i => BigDecimal(i)
val displayAccount : Money => Message = m => s"account status : "+m

Those functions are simple to use and easy to compose

val composed: Input => Message =createMoney andThen displayAccount

Partial Functions and Effects

But what if not for every input there is a valid output? When we are at the border of the system then we are not in control of what data is send to it or whether we are able to connect with external infrastructure.

type RawInput = String
val rawInput : String = "100"
val rawInput2 : String = "ab"
val read : RawInput => Input = ri => ri.toInt

read(rawInput)
read(rawInput2) //exception

For each not supported input we can have an exception that why additional logic dependent on context is necessary. And because of this context dependency it is difficult to compose those functions in general.

There is a solution. We can represent "unsupported inputs" by special types which has value for every input - that way we will turn partial function into total function again. There are various effect represented by different types

val effectOfMissingValue:Option[String] = None
val effectOfFailure:Try[Input] = Try{read(rawInput2)}

import scala.concurrent.ExecutionContext.Implicits.global
val effectOfTime : Future[Input] = Future{TimeUnit.MILLISECONDS.sleep(100);read(rawInput)}

//total function again
val safeRead : RawInput => Try[Input] = ri=>Try(ri.toInt)

Composing effects and pure functions

Along with effect types there are special operators for combining them with pure functions. One of the simplest is map which modify potential internal value according to pure function.

val safeRead : RawInput => Try[Input] = ri=>Try(ri.toInt)
val display1: Try[Message] =safeRead(rawInput).map(createMoney).map(displayAccount)
Unpacking Types and Pattern Matching

Finally when we transform effect according to business rules we can unpack it on another system border

def sideEffectAwareBorderMethod : Try[Message] => Unit = {
   case Success(message) => println(s"RESULT OF OPERATION : $message")
   case Failure(e) => println(s"ERROR : $e")
}

sideEffectAwareBorderMethod(display1)

Referential Transparency

we saw that we can transform values by sequencial function infocation with operator map :

effect.map(f).map(g)

However if f _and _g don't have any visible side effects and also map _doesn't have any visible side effects, then you can write it this way_

effect.map(f antThen g)

effect.map(g compose f)

But if there is no difference then why bother? .The keyword here is visible side effect . Depending on effect map may change thread context or iterate through million elements of a list so having the same result we should observe better performance with second approach.

Currying

In the last section of this module we will see very handy technique which allow to configure functions. The concept is simple - we create a function which returns a function

A=>B=>C

By applying param A we receive configured function.

  val calculateTax : Tax => Money => Money = tax => m => m+ (m*tax)
  val calculateGross: (Money) => Money =  calculateTax(0.19)
  val calculateVat: (Money) => Money =  calculateTax(0.23)

EXERCISES

FILE : SeparateDomainFromEffectsAnswers

EXERCISE1 - compose business functions with Option effect

E1A

We are emulating database with simple map Int=>User

val database = Map[Int, User] {
        1 -> User(1, "Mirek", 50)
}
  • complete createMessage function which is pure business function
  • complete effectFunction which operates on Option directly. You need to handle two cases Some _and _None

E1B

Here we are going to use currying ti understand how to combine an effect with multi argument functions. Again we are emulating database with simple map.

val config = Map {
        "taxes.gross" -> 0.19
}

'Map' in scala returns Option[A] which is Empty when value is missing or _Some(a) _when value is present.

  • write curried business function which injects tax and calculate gross value
  • write effect function 'toDecimalFormat' _which will convert _Option[Money] to BigDecimal

E1C

This exercise is different. To learn that you can adapt any function to you need write a custom higher kind function which is able to swap arguments of function passed as an argument :

 def swap[A, B, C](f: A => B => C): B => A => C = ???

It may be used to adapt more general gross function from previous exercise

EXERCISE2 - Exception as an Effect with Try and Combining effects.

E2A

Finish method

def parseHex(s: String): Try[Int]

which is defined at the bottom of the file so that first test from Exercise 2 passes. This will be main factory method for hex type

E2B

Try to add two elements with pattern matching. Till now we just operated on single effect and pure functions so for the first time we will try to actually work on multiple effects.

At first with _brute force. _Write total function which can handle each failure possibility.

def add(i1: Try[Int], i2: Try[Int]): Try[Int] = (i1, i2) match {
          case (Success(a), Success(b)) => ???
          case (Success(_), f: Failure[_]) => ???
          case (f: Failure[_], Success(b)) => ???
          case (f, _) => ???
}

E2C

There is also wisdom in being wrong. So here we will hit dead end by trying to compose effects with map operator.

So be prepare to see types like

 val r1: Try[Try[Int]]

and being unsuccessful by trying to fail external type when you have

Success[Failure[Int]]

E2D

Meet first powerfule FP abstraction - Applicative Functor

Compose independent effects with map2 operator

import cats.Apply
import cats.instances.try_._
Apply[Try].map2

E2E

If you are concerned by use of advanced FP library cats - don't be. In case of need you can define your own FP operators.

def map2[A, B, C](a: Try[A], b: Try[B])(f: (A, B) => C): Try[C] = ???

EXERCISE3 - time effect

There is only one exercise :

  • test is ready
  • Database object is ready
  • you have to complete Exercise3Module
    • complete getters Email and Name
    • define a campaing which checks if given customer purchased product for a price greater that one provided
    • finally complete function which creates promotional email

Database simulates wait time - observe how future abstraction is used to compose pure functions with time.

Smart Constructors

FILE : SmartConstructorsDemo

Smart constructors are guarding system from wrong data variants like for example 11th day of week or negative number of meeting participants.

First example _Day of the week _is taken literally from The Book "Functional and Reactive Domain Modeling". We can see how an object can take a role of factory and how effect shows possible result of data creation which may end in failure.

 object DayOfWeek{
    private def unsafeDayOfWeek(d: Int) = new DayOfWeek { val value = d }
    private val isValid: Int => Boolean = { i => i >= 1 && i <= 7 }
    //more powerful types than option in workshops about ADT
    def dayOfWeek(d: Int): Option[DayOfWeek] = if (isValid(d))
        Some(unsafeDayOfWeek(d)) else None
  }

Meetup

In the second part of the demo we will see bigger example when we want to create a meetup. There are some rule according to meetup can be created and we can add special type alias to simplify signatures.

private type MeetupCondition[A] = A => Boolean

private val noBanned: Set[User] => MeetupCondition[Set[User]] =
        banned => signed => banned.intersect(signed).isEmpty

private val maxUsers: Int => MeetupCondition[Set[User]] =
        maxAllowed => us => us.size <= maxAllowed

private val notWeekends: MeetupCondition[Option[DayOfWeek]] = day =>
        day.exists(d => d.value < 6)

we can see that for noBanned _and for _maxUsers we used currying to inject initial params. The question now is how to inject those value if mentioned functions are private fields of a MeetumModule?

We can again use currying on a method level

 def meetup(max: Int, banned: Set[User])(topic: String, us: Set[User], day: Option[DayOfWeek])

Which give us preconfigured factory

val meetupFactory: (String, Set[User], Option[DayOfWeek]) => Option[Meetup] =
Meetup.meetup(4,Set(User("banned","[email protected]")))

//and then
  val m1: Option[Meetup] =meetupFactory("bridge game",Set(User("user1","[email protected]")),DayOfWeek.dayOfWeek(1))

EXERCISES

Exercise1 - create objects with smart constructors

E1A

In this simple exercise you need to create smart constructor which make unavailable to create salary with negative value.

E1B

This time you need to create factory method which will construct tax in given range. This factory method will be curried so we are unable to raise an exception when only first parameters are defined. We will solve this issue in the next exercise

E1C

Here we will define Range with its own type - this way we will solve problem from the previous exercise - now we have separate Smart Range constructor.

Exercise2 - encapsulate type creation

In this part we have only one exercise .You will have to use Apache Common Codec _to safely convert between _String _and dedicated _HexType

Exercise3 - Smart Constructors and Dependant effects

This is the biggest exercise in this Section. We have two modules Accounts and second smaller one FunctionalLibrary . You will have to create couple factory methods which will produce arguments for other factory methods. For the first time one effect will be dependant on result of other effect so we will have a short peek at new powerful abstraction.

E3A

There is Smart Constructor for Money type ready. Create pure business function which creates Specific type of account according to available Money. Notice sealed _trait which limit subtypes of accounts to just _Standard _and _Premium.

E3B

Here we will just test transaction method. In this exercise you will see that for testing purpose _package private _scope is very practical so you can create Specific objects directly by using unsafe constructors available only within package.

E3C

Time to perform transaction (which returns Try) by first creating Money and Accounts (which also returns Try) . Make first unsuccessful attempt to compose dependant effects.

E3D

Implement new powerful operator flatMap which allow to compose dependant effect. In this exercise you will compose two effects with curried function which applies values from effects one by one

E3F

Finally use flatMap to perform transaction after creating Accounts and Money to transfer

results matching ""

    No results matching ""