- references
- http://blog.higher-order.com/assets/fpiscompanion.pdf
- https://typelevel.org/cats/typeclasses/functor.html
- https://typelevel.org/cats/typeclasses/monad.html
- https://stackoverflow.com/questions/14598990/confused-with-the-for-comprehension-to-flatmap-map-transformation
- https://docs.scala-lang.org/tutorials/FAQ/yield.html
- https://www.james-willett.com/scala-map-flatmap-filter/
- https://miklos-martin.github.io/learn/fp/2016/03/10/monad-laws-for-regular-developers.html
- https://typelevel.org/blog/2016/08/21/hkts-moving-forward.html
- https://netvl.github.io/scala-guidelines/type-system/higher-kinded-types.html
- https://dzone.com/articles/application-type-lambdas-scala-0
- https://carlo-hamalainen.net/2014/01/02/applicatives-compose-monads-do-not/
- https://github.com/kitlangton/zio-from-scatch
- https://www.manning.com/books/functional-programming-in-scala-second-edition
- https://github.com/fpinscala/fpinscala/wiki
- Scala with Cats Book - Noel Welsh
- Functional Programming in Scala - Paul Chiusano
- ZIO from Scratch — Part 1
- ZIO from Scratch — Part 2
- Software Transactional Memory
- A Pragmatic Introduction to Category Theory—Daniela Sfregola
- https://chat.openai.com/
 
- goals of this workshop
- introduction to scala features:
- for-comprehension
- higher kinded types
 
- developing basic intuitions concerning standard functional structures
- monoid
- functor and applicative functor
- monad
 
- show fundamental differences between above constructs
- apply knowledge to real life
- functional approach to validation
- functional approach to IO
 
 
- introduction to scala features:
- workshops order
- ForComprehensionWorkshop
- MonoidWorkshop
- FunctorWorkshop
- ApplicativeWorkshop
- PersonValidatorWorkshop
- MonadWorkshop
- EchoWorkshop
 
- whenever we create an abstraction like Functor we should
- consider abstract methods it should have
- and laws we expect to hold for the implementations
- of course Scala won’t enforce any of these laws
 
 
- laws are important for two reasons
- help an interface form a new semantic level
- algebra may be reasoned about independently of the instances
- example
- we could proof that Monoid[(A,B)]constructed fromMonoid[A]andMonoid[B]is actually a monoid- we don’t need to know anything about AandBto conclude this
 
- we don’t need to know anything about 
 
- we could proof that 
 
- we often rely on laws when writing various combinators
- example
- unzip List[(A, B)]intoList[A],List[B]- same length
- corresponding elements in the same order
 
- algebraic reasoning can potentially save us a lot of work, since
we don’t have to write separate tests for these properties
- we could write a generic unzip function that works for any functor
 
 
- unzip 
 
- example
 
- help an interface form a new semantic level
- monoid consists of the following:
- trait
trait Monoid[A] { def combine(a1: A, a2: A): A def zero: A }
- laws (semigroup with an identity element)
- associativity
combine(combine(x,y), z) == combine(x, combine(y,z)) for any choice of x: A, y: A, z: A
- identity
exists zero: A, that combine(x, zero) == x and combine(zero, x) == x for any x: A
 
- associativity
- is a type A and an implementation of Monoid[A]that satisfies the laws
 
- trait
- example
- string monoid
val stringMonoid = new Monoid[String] { def combine(a1: String, a2: String) = a1 + a2 val zero = "" }
- various Monoid instances don’t have much to do with each other
- monoid is a type, together with the monoid operations and a set of laws
- you may build some intuition by considering the various concrete instances
- but nothing guarantees all monoids you encounter will match your intuition
 
 
- we can say that
- type A forms a monoid
- type A is monoidal
- less precisely: type A is a monoid or even type A is monoidal
- but not: type A has monoid
- analogy: the page you’re reading forms a rectangle, is rectangular or it is a rectangle but not: it has a rectangle
 
- in any case, the Monoid[A] instance is simply evidence of this fact
 
 
- string monoid
- folding context
and we want to fold into the same type sodef foldRight[B](z: B)(f: (A, B) => B): Bmonoid fit these argument types like a glovedef foldRight[A](z: A)(f: (A, A) => A): AfoldRight(monoid.zero)(monoid.combine)foldLeftandfoldRightgives the same results when folding with monoid (associativity)
- parallelism
- below three operations give the same result
combine(a, combine(b, combine(c, d))) combine(combine(combine(a, b), c), d) combine(combine(a, b), combine(c, d)) // allows for parallelism
- proof (associativity)
combine(combine(combine(a, b), c), d) = combine(combine(x, y), z), where x = combine(a, b), y = c, z = d combine(combine(x, y), z) = combine(x, combine(y, z)) combine(x, combine(y, z)) = combine(combine(a, b), combine(c, z)) so: combine(combine(combine(a, b), c), d) = combine(combine(a, b), combine(c, d))
 
- below three operations give the same result
- additional properties
- monoids compose
- example: A, B monoids -> (A, B) is also a monoid
 
- example: 
- monoid homomorphism
- condition: A, B monoids => A.combine(f(x), f(y)) == f(B.combine(x, y))
 
- condition: 
- monoid isomorphism
- homomorphism in both directions
- example: StringandList[Char]monoids with concatenation
 
- commutative combine
- when combine(x, y) == combine(y, x))
 
- when 
 
- monoids compose
- semigroup
- semigroup for a type A has an associative combine operation that returns an A given two input A values
trait Semigroup[A] { def combine(a1: A, a2: A): A }
- example: NonEmptyList[A]- there is no instance Monoid[NonEmptyList[A]]- you cannot specifyzeroelement
 
- there is no instance 
 
- semigroup for a type A has an associative combine operation that returns an A given two input A values
- definition
- we say that a type constructor Fis a functor, and theFunctor[F]instance constitutes proof thatFis in fact a functor
 trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] }
- we say that a type constructor 
- must obey two laws:
- fa.map(f).map(g) = fa.map(f.andThen(g))
- fa.map(x => x) = fa
 
- functors compose: https://github.com/mtumilowicz/scala212-cats-category-theory-composing-functors
- for more formal reasoning, please refer: https://github.com/mtumilowicz/java11-category-theory-optional-is-not-functor
- definition
trait Applicative[F[_]] { def unit[A](a: => A): F[A] def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] }- could be formulated using unitandapply(therefore called Applicative), rather thanunitandmap2- def apply[A,B](fab: F[A => B])(fa: F[A]): F[B]
 
 
- could be formulated using 
- applicative laws
- left and right identity
in other wordsmap(v)(id) == v map(map(v)(g))(f) == map(v)(f compose g)map2(unit(()), fa)((_,a) => a) == fa map2(fa, unit(()))((a,_) => a) == fa
- associativity (in terms of product)
- def product[A,B](fa: F[A], fb: F[B]): F[(A,B)] = map2(fa, fb)((_,_))
- def assoc[A,B,C](p: (A,(B,C))): ((A,B), C) = p match { case (a, (b, c)) => ((a,b), c) }
- product(product(fa,fb),fc) == map(product(fa, product(fb,fc)))(assoc)
 
- naturality law
- fa.map2(fb)((a, b) => (f(a), g(b))) == fa.map(f).product(fb.map(g))
 
 
- left and right identity
- all applicatives are functors
- advantages
- preferable to implement combinators using as few assumptions as possible
- it’s better to assume that a data type can provide map2thanflatMap
- otherwise we’d have to write a new traverseevery time we encountered a type that’s Applicative but not a Monad
 
- it’s better to assume that a data type can provide 
 
- preferable to implement combinators using as few assumptions as possible
- validation context
- only reporting the first error means the user would have to repeatedly submit the form and fix one error at a time
- consider what happens in a sequence of flatMapcalls like the followingvalidName(field1) flatMap (f1 => validBirthdate(field2) flatMap (f2 => validPhone(field3) map (f3 => ValidInput(f1, f2, f3))- if validNamefails with an error, thenvalidBirthdateandvalidPhonewon’t even run
 
- if 
- now think of doing the same thing with map3map3(validName(field1), validBirthdate(field2), validPhone(field3))(ValidInput(_,_,_))- no dependency implied between the three expressions
 
 
- additional properties
- large number of the useful combinators on Monad can be defined using only unit and map2
- example
def map2[B, C](fb: F[B])(f: (A, B) => C): F[C] = fa.flatMap(a => fb.map(b => f(a, b)))
 
- example
- applicative functors compose
- F[_]and- G[_]are applicative functors =>- F[G[_]]is applicative functor
 
 
- large number of the useful combinators on Monad can be defined using only unit and map2
- definition
trait Monad[F[_]] { def unit[A](a: => A): F[A] def flatMap[A, B](ma: F[A])(f: A => F[B]): F[B] }
- monad laws
- given compose function
def compose[A,B,C](f: A => F[B], g: B => F[C]): A => F[C] = a => flatMap(f(a))(g)- digression
- functions like that: A => F[B]are called Kleisli arrows
 
- functions like that: 
 
- digression
- left identity and right identity
compose(f, unit) == f // m.flatMap(unit) == m compose(unit, f) == f // unit(a).flatMap(func) == func(a) // example for option option.flatMap(Some(_)) == option Some(value).flatMap(f) == f(value)- similar to zero element in monoids
 
- associative law for monads
compose(compose(f, g), h) == compose(f, compose(g, h)) // example for option option.flatMap(f).flatMap(g) == option.flatMap(f(_).flatMap(g))- similar to associative law for monoids
 
 
- given compose function
- monad is an implementation of one of the minimal sets of primitive combinators
satisfying the monad laws
- combinator sets
- unit and flatMap
def unit[A](a: => A): F[A] def flatMap[A, B](ma: F[A])(f: A => F[B]): F[B]
- unit and compose
def unit[A](a: => A): F[A] def compose[A,B,C](f: A => F[B], g: B => F[C]): A => F[C]
- unit, map and join
def unit[A](a: => A): F[A] def map[A, B](fa: F[A])(f: A => B): F[B] def join[A](mma: F[F[A]]): F[A]
 
- unit and flatMap
 
- combinator sets
- each monad brings its own set of additional primitive operations that are specific to it
- example: Option,Either,List
 
- example: 
- vs interfaces
- interfaces ~ relatively complete API for an abstract data type
- merely abstracting over the specific representation
- example: List - LinkedList, ArrayList
 
- monad ~ many vastly different data types can satisfy the Monad interface and laws
- like Monoid, is an abstract, purely algebraic interface
 
 
- interfaces ~ relatively complete API for an abstract data type
- chain of flatMapcalls is like an imperative program with statements that assign to variables- monad specifies what occurs at statement boundaries
- example
- Optionmonad - may return None at some point and effectively terminate the processing
 
- example: identity monad
case class Id[A](value: A) { def map[B](f: A => B): Id[B] = Id(f(value)) def flatMap[B](f: A => Id[B]): Id[B] = f(value) }val id = Id("Hello, ") .flatMap(a => Id("monad!") .flatMap(b => Id(a + b))) // id = Id(Hello, monad!)- simply variable substitution
vsfor { a <- Id("Hello, ") b <- Id("monad!") } yield a + bval a = "Hello, " val b = "monad!" val c = a + b- variables aandbget bound to"Hello, "and"monad!”
 
- variables 
- monads provide a context for introducing and binding variables, and performing variable substitution
 
- simply variable substitution
 
- unlike Functors and Applicatives, not all Monads compose
- to show that "monads do not compose", it is sufficient to find a counterexample, namely two monads
fandgsuch thatf gis not a monad
- proof: https://carlo-hamalainen.net/2014/01/02/applicatives-compose-monads-do-not/
- problem
- to implement joinfor nested monadsFandG, write something of a type like:F[G[F[G[A]]]] => F[G[A]]
- that can’t be written generally
- but if Galso happens to have aTraverseinstanceF[F[G[G[A]]]] => F[G[A]] // use sequence to turn G[F[_]] into F[G[_]], then use join on F then on G
 
- to implement 
- problem is often addressed with a custom-written version specifically constructed for composition
- example: monad transformers like OptionT
 
- example: monad transformers like 
 
- to show that "monads do not compose", it is sufficient to find a counterexample, namely two monads
- all monads are applicatives
- not all applicatives are monads
- difference between monads and applicatives
- applicative constructs context-free computations, while Monad allows for context sensitivity
- joinand- flatMapcan’t be implemented with just- map2and- unit- def join[A](f: F[F[A]]): F[A] // removes a layer of F
- unitfunction only lets us add an- Flayer
- map2lets us apply a function within- Fbut does no flattening of layers
 
 
- Optionapplicative versus the- Optionmonad- combine the results from two (independent) lookups: map2val F: Applicative[Option] = ... val departments: Map[String,String] = ... val salaries: Map[String,Double] = ... val o: Option[String] = F.map2(departments.get("Alice"), salaries.get("Alice")) { (dept, salary) => s"Alice in $dept makes $salary per year" }
- result of one lookup to affect next lookup: flatMapval idsByName: Map[String,Int] val departments: Map[Int,String] = ... val o: Option[String] = idsByName.get("Bob") .flatMap { id => departments.get(id) } }
 
- combine the results from two (independent) lookups: 
 
- each monad brings its own set of additional primitive operations that are specific to it
- example: State[S, A]def get[S]: State[S, S] def set[S](s: => S): State[S, Unit]
 
- example: 
- monad is a monoid in a category of endofunctors
- endofunctor - functor that maps a category to itself
- let's look for minimal set of combinators for a monad: unit, map and join
- monoid = unit + join
- endofunctor = map
 
- Monoid[M]- objects: Scala types, arrows: Scala functions
- Monad[F]- objects: Scala functors, arrows: natural transformations
- overview
zero/unitop/joinMonoid[M]1 => MM² => MMonad[F]1 ~> FF² ~> F- ~>is natural transformation
- M²is- (M, M)
- F²is- F[F[A]]
- 1is- for Monoid: Unittype
- for Monad: identity functor
 
- for Monoid: 
 
 
sealed trait IO[A] {
  self => //  lets us refer to this object inside closures
  def unsafeRunSync: A
  def map[B](f: A => B): IO[B] =
    new IO[B] {
      def run: B = f(self.run)
    }
  def flatMap[B](f: A => IO[B]): IO[B] =
    new IO[B] {
      def run: B = f(self.run).run
    }
}
- in short: IOis a magic thing that says this function depends on something other than its arguments
- clearly separates pure code from impure code, forcing us to be honest about where
interactions with the outside world are occurring
- referentially transparent description of a computation with effects
 
- IO computations are ordinary values
- we can store them in lists, pass them to functions, create them dynamically, and so on
- example
def PrintLine(msg: String): IO = new IO { def unsafeRun = println(msg) } def contest(p1: Player, p2: Player): IO = // pure function PrintLine(winnerMsg(winner(p1, p2)))
 
- given IO[A]will overflow the runtime call stack and throw aStackOverflowError- solution: encapsulate operations into dedicated case classes and use tail recursion for evaluation
- io.flatMap(f).flatMap(g) = io.flatMap(v => f(v) flatMap g)
- approach called trampoline
 
 
- solution: encapsulate operations into dedicated case classes and use tail recursion for evaluation
- represent an ability to abstract over type constructors
- suppose we have same implementations, different type constructors
def tuple[A, B](as: List[A], bs: List[B]): List[(A, B)] = as.flatMap{a => bs.map((a, _))} def tuple[A, B](as: Option[A], bs: Option[B]): Option[(A, B)] = as.flatMap{a => bs.map((a, _))} def tuple[E, A, B](as: Either[E, A], bs: Either[E, B]): Either[E, (A, B)] = as.flatMap{a => bs.map((a, _))}- in programming, when we encounter such great sameness—not merely similar code, but identical code—we would like the opportunity to parameterize: extract the parts that are different to arguments, and recycle the common code for all situations
- we have a way to pass in implementations; that’s just higher-order functions
- we need "type constructor as argument"
def tuplef[F[_], A, B](fa: F[A], fb: F[B]): F[(A, B)] = ???- F[_]means that- Fmay not be a simple type, like- Intor- String, but instead a one-argument type constructor, like- Listor- Option
 
 
- higher-kinded types are sometimes used in libraries
- example: standard Scala collections
 
- Scala doesn’t allow us to use underscore syntax to simply say State[Int, _]to create an anonymous type constructor like we create anonymous functions- we have to go through type projections
- ({type L[a] = Map[K, a]})#L- declares an anonymous type
- then access its Lmember with the#syntax
- type constructor declared inline like this is often called a type lambda in Scala
 
 
- removed in Scala 3
 
- we have to go through type projections
- each line in the expression using the <-is translated to aflatMapcall, except- the last line (yield) - it is translated to a concludingmapcall
- x <- c if condis translated to- c.filter(x => cond)
 
- the last line (
- flatMap/- map- for { bound <- list out <- f(bound) } yield out // is equivalent to (could be desugared with IntelliJ) list.flatMap { bound => f(bound).map { out => out } }
- flatMap/- map/- filter- for { sl <- l el <- sl if el > 0 } yield el.toString.length // is equivalent to l.flatMap(sl => sl.filter(el => el > 0).map(el => el.toString.length))