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