Skip to content

Instantly share code, notes, and snippets.

@pierangeloc
Last active December 15, 2017 08:37
Show Gist options
  • Save pierangeloc/a2a1289f7885d46fe966bae653f04acc to your computer and use it in GitHub Desktop.
Save pierangeloc/a2a1289f7885d46fe966bae653f04acc to your computer and use it in GitHub Desktop.
ScalaX-2017-day-1

ScalaX 2017

Bartosz Milewski: Cat theory

Cat Theory: science of composition. Most of maths can be based on it, and programming as well.

Compose and abstract. We need to abstract because we can't hold more than 2-3 things and details at time in our head (short memory). We can't check every time the impl details.

FP

f: A => B. Can either base on Set theory, or forget about details and focus on substance, and I have object/arrows and properties.

Void or empty set is an initial object. It has an arrow that goes to any other object (in the category of all possible types). For every type A, there is one and only one function going from Void to A. There are no functions that can target this Void, because this would imply the possibility of constructing an element from this type. There is a function absurd[A]: Void => A but it cannot be called! Sourcing a type is called Elimination. Targeting from a type is called Introduction.

Unit: we reverse all the arrows. Unit is an object that has arrows from any other object. It is a set of just one element. Introduction: unit[A]: A => Unit; Elimination: Unit => A There are many functions that implement the Elimination, as many as the #A

Product types

Given A and B I want to define a product C such that there is f: C => A and g: C => B and we have h(c: C) = (f(c), g(c)). There are many of these candidates C, but the best ones are those that allow me to get the full set A from the application of h. If there is any other candidate C' there is by definition a function that factorizes the initial ones. This defines Product in terms of compositions.

Sum types

Reversing arrows we get to sum types.

With Void -> 0, Unit -> 1, Product -> x, Sum -> + => We have 2 monoids. This is enough to build basically all types:

Bool = True | False Nat = Unit | (Unit | (Unit | (Unit | Same for Option[A], or List[A] (which is a recursive type)

Functors

We have 2 categories, each with its own arrows and objects. Can I define a category between the objects of Cat1 and Cat2. We want that this functor is able to map the arrows of Cat1 to those of Cat2. If exists f: A => B, there is a F[A => B]: F[A] => F[B]

...and then I get lost with adjunctions :-)

Mapping Functors to Functors

Natural transformations. Given F[A] ~> G[A], it must exist for all A! E.g. map List[_] ~> Option[_] by headOption, it works for all A!

Conclusion

Using cats, I can define any type. We can define function types out of it, then abstract on functions between categories called functors. If src and target cat is the same we have endofunctors (and monads) In the cat of functors I can define arrows as nat transformations.

Manuel Bernhardt: Akka anti-patterns

1. Global mutable state

If hold state inside actor it's ok. Actor has total control over state, fine.

How to break this: I have a state that refers an external entity that is referenced by other actors. E.g. close over mutable state in async calls, or closing over actor context, or sender. Or passing mutable state to constructor of Actor.

Why is this bad? Actors don't control the thread they are working on, therefore no ThreadLocal!!! What to do:

  • use immutable messages and data structures.
  • Ask actors to provide state information
  • for async calls, use always the pipeTo pattern! And always add a recover for timeout for the future, otherwise it will send a message that is without specific context

2. Avoid systems that have no hierarchy

Don't put too many actors under /user level. The parent actor must take care of exceptions sent by children actors. The /user default mechanism just restarts actors and they loose state.

We should focus on happy path, let it fail! Don't recover, the part out of band, e.g. the supervisor, will take care of handling the error.

3. Avoid too many Actor Systems

We want stuff to be bulk headed from one another. But keep in mind: each AS is backed by a ThreadPool, that contend each for cores.

What to do: Trust akka, bulkhead with custom dispatchers. Config: define contexts.mydispatcher.thread-pool-executor.thread-size and when instantiating actor use `Props[MyActor].withDispatcher("context.mydispatcher")``

4. Avoid log anti-patterns

  1. Avoid string concat, rather use curly braces
  2. Avoid non-async logging
  3. Avoid debugging level in prod
  4. Use Logstash

5. Avoid nesting VM

e.g. Fork-join executor by default: With all the virtualization around it, we neeed to know how many actual cores the application is granted => much context switching.

6. Avoid blocking

Call async lib => async lib => blocking call!!! How do I find this? That's difficult.

Don't await for futures. Context switching costs few order of magnitude. If you have to block, do it on dedicated dispatcher. Use the akka scheduler if ou are awaiting for something (sending a reminder and checking periodically rather than blocking, and mark it as a control message).

7. Avoid using akka-remoting directly, use Akka Cluster!

If one of the 2 systems downs, the system 1 must be restarted to be able to handle this.

We rather need a failure detector, we need a gossip protocol under it. Akka-Cluster helps, it gives you all this.

Akka-Cluster is not a silver bullet though, we can have a split brain => use split brain resolver

8. Avoid Java Serialization

Don't leave the default on. Never.

It is bad for protocol evolution. Use always something that is bacwards compatible. Avro has schema registry, backwards compatibility guaranteed.

Heiko: Typed actors

Behavior of actor: what actor can do:

  • create new actors
  • change behavior for next messages
  • change its state and interact with external systems

Distr systems are about protocols, not about implementation.

In legacy actors I can model behavior by receive and changing receive

In Typed Akka:

  • I have no ActorRef but ActorRef[T], where T is the type actor is able to handle
  • I don't extend Actor but I extend Behavior[T]
  • I don't have anymore implicit sender() nor ActorSelection, because the comppiler doesn't know what sender is sednign the message therefore at compile time we can't know the type the sender is able to handle
  • Behavior[T] is a Message => Behavior[N]. Messages are either of type T or of type Signal
  • I use Actor.immutable if the actor doesn't have state. NB. as there is no sender, I need to pass the sender necessraily in the message itself!!!
  • ==> cmd GetBalance becomes GetBalance(replyTo: ActorRef[Balance])
  • ==> cmd Withdraw becomes Withrdraw(amount: Long, replyTo: ActorRef[WithdrawReply] where WithdrawReply = Withdrawn | InsuffBalance(value: Long (pardon my Haskell)
  • To create the actor I create some method that returns a Behavior[Command]
  def apply(b: Long): Behavior[Command] = Actor.immutable {
  case (_, GetBalance(replyTo: ActorRef[Balance]_)
}
  • Testing is slightly different
  • If I want to ignore some messages, I use Actor.immutablePartial to ignore some messages
  • Actor.deferred to defer to children actors
  • Actor.onSignal for actors that handle signals
  • To create children use ActorContext.spawn
  • To create an actor able to receive messages that I'm not able to handle, Context.spawnAdapter{case ExternalMs => InternalMsg etc...}
  • Adapters give us type safety with relatively low boilerplate
  • There is akka.typed.scaladsl.adapter._ that when imported allows to mix typed and untyped actors together
  • Supervision: Faulty actors are not restarted, but STOPPED! This is a great behavior we always tend to apply to our (untyped) actors. contextspawn(Actor.supervise(MyTypedActor).onFailure(exception...)
  • What is available so far: there are Typed API for Akka persistence, Cluster, Sharding, CRDT. Implementation is still based on untyped. Be there at workshop on Saturday!
  • Aim: end Q1-2018 experimental and Prod ready.

Debasish Ghosh: Architectural patterns

Defs:

  • Patterns: Mean Algebraic pattern, that can be used on all ctx. Different implementations lead to different implementations, but algebra still holds
  • Modularity: Build small features to compose and build bigger ones. We want pure APIs, functional
  • Domain model: nothing to do with solution architecture, it is the business domain

We abstract Side effects with pure values that we call effects.

Domain modeling

Make side effects tractable through effects. Is Domain model a function? No. But models inside the full DM is a Bounded Ctx that is composed of:

  • Domain objects named after consistent vocabulary
  • Rules that are modeled through functions and properties these functions honor. Functions are closed under composition
  • All this builds an algebra
  • A strongly typed language allows to express some invariants through type constraints
  • Some other rules can be tested through Property Based testing

Typically algebras are abstract on the type. E.g. a Monoid or Foldable are generic on type. Then I can derive a mapReduce algebra given the ones of Foldable and Monoid

Algebras are strictly reusable. Domain model is union of Domain Algebras (one per domain model)

Takeaways

  1. Be explicit about your algebra, use types names from DDD
  2. Group functions in modules
  3. Group modules

Composition of these functions, and Side-Effects

Side effects will be there (DB, logs, partiality, exceptions). Side effects are anti-patterns for modularity, they break RT. Solution: Abstract them into Data types, and compose them. These Data type constructors we call Effect. Reader, State, IO, Writer all have shape of F[A] where F deals with context, and A with the value inside the context.

We keep the algebra open for interpretation as long as possible. E.g. in test I can be happy with Id rather than Future monad

I want to be explicit about which property I need to be able to e.g. compose or parallelize my computations.

Let's commit as late as possible about the concrete effect we want to commit to. This allows us to focus on the storytelling, i.e. on the what and forget about the how. Optimize, compose later on.

Result: I can compose stuff. I want to plugin in new algebras together and e.g. abstract the logging the logs and then wirigin all together. I can test that I am logging properly!!!

Advantage of Free over tagless final: it's stack safe, and it can allow to build an AST for the program and do optimizations (e.g. based on known business rules)

Other options is Eff.

Takeaways

  • Use the least possible abstraction
  • Parameterize on types as much as possible
  • Commit on types as late as you can

Miles on Shapeless

Issues on Shapeless: Compile time long. Lazy works with recursive types but it's very dirty under the hood. Macros are not portable.

To fix these issues probably we need to work on the compiler itself

Better error handling (Benjamin Parker)

Things we care about:

  • Compilation safety
  • Naming
  • Formatting

Use Either, with typed error and typed Right part. How do we compose stuff that have different Left parts, being that each function deals with different apis? How do we put this in a for comprehension? The left types must be aligned and they aren't.

One solution is to have all our error types to extend the same error, e.g. DBError extends MyError, and WSError extends MyError. I would have to make all errors in 1 file, with sealed trait MyError, and this can make this file huge to cover all possible errors.

When calls are async we have Future[Either[Error, User]] and I have extra noise, while I care only about User. Future can have success or failure, and to map to our Either we can always use the .recover and go back to our Future[Either[_, _]]. But what if I forget? How can I have some guarantees?

Other problem: I have to map the value on the right part. I need a map with a map inside, it's a mess and very error prone.

Use EitherT

This way I can pretend the future is not there. But the naming is not immediate, and e.g. a junior can have trouble with it. Formatting is great and back to shape. Compilation fails though as EitherT is invariant on error. and I have to use EitherT[Fut, MyError, A] and I lost my type safety

I'd like to be able to use Union Types, avoiding that sealed trait... and rather have a type that says A | B | C. The for comprehension could automatically map to this union type and I don't loose any compilation check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment