Showing posts with label functional programming. Show all posts
Showing posts with label functional programming. Show all posts

Friday, January 10, 2025

The New Logging

Twenty years ago, everybody was writing their own logging framework. Today, when distributed computing is the norm, distributed logging is a necessity.

Functional Programmer really comes into its own in such an environment. So, it's no surprise that the FP tools already support distributed logging. In the Cats ecosystem, there is Natchez. This can feed into distributed tracing system like the open source Jaeger, a Go application that can store the data in Elastic or Cassandra. Zipkin is another if you prefer a Java implementation. 

[Regarding Natchez "unless you have a specific reason to want to use Natchez, you may want to look at otel4s instead. I think the community will be migrating to otel4s once it is binary-stable (Natchez is great and still works well but if you're starting fresh you may as well use the library that implements the industry standard for tracing)" - Discord]

MDC

First, some terminology.

"Mapped Diagnostic Context is to provide a way to enrich log messages with pieces of information that could be not available in the scope where the logging actually occurs, but that can be indeed useful to better track the execution of the program." [Baeldung]

Aside: don't use this mechanism in your business logic code.
Fabio Labella @SystemFw Jan 13 17:20
I don't know how many details I can give, but basically it got to the point that C-level executives knew the word "ThreadLocal", which is really bad. But basically someone had built an entire internal framework based essentially on MDC for a lot of business logic in a multitentant environment (falling down the slippery slope that it was "context"), and that at some point there were a crap load of race conditions due to ThreadLocal + asynchrony, risking crosstalk, which in a financial institution can well mean your whole company gets shut down
Spans and Kernels

"The usual tracing approach involves threading spans (aka the context) throughout the application we wish to instrument. On the other hand, distributed tracing requires a so-called kernel to be able to continue the previous tracing span." - Functional Event Driven Architecture, Volpe.

Cats

Within an effects engine, ThreadLocal becomes the wrong tool. You can use Task Local in Monix to create MDC functionality.
Gerry Fletcher @gerryfletch Jan 13 17:17
It's purely to add the request id into every log line
Fabio Labella @SystemFw Jan 13 17:17
log4cats has a withContext that lets you do that without relying on state
In "Practical FP in Scala", Gabriel Volpe says:
Normally, people use the Slf4j implementation, which is created as shown below. 
implicit val logger: Logger[IO] = Slf4jLogger.getLogger[IO]
In our application, we are going to use Log4cats , which is by now the standard logging library of the Cats ecosystem.Whenever we need logging capability, all we need is to add a Logger constraint to our effect type. For instance:

def program[F[_]: Apply: Logger]: F[Unit] =
  Logger[F].info("starting program") *> doSomething
The idea is that your code logs as normal but transparently these events are sent to some aggregator. What's more, the event are contextual - they might take place over several nodes in a cluster.

The FP magic in the above Logger[F].info("... code is that there is an implicit Logger in the ether (this code's enclosing scope will define [F[_]: Logger: ...). This Logger might just write to disk or might send the message to an external system. This calling code doesn't care. The relevant Logger is merely summoned.

Regarding architecture:
Christopher Davenport @ChristopherDavenport Jan 13 17:49
So I can write middlewares on my logger and clients that enhance them with aspects about the user and then I can queue them into my in memory queue for when its something thats decoupled from response cycle. I keep all the logging across the entire stack, even after submission to a queue as a result.
Including things like request-id, user-information, and can continue to propogate that information to other services for tracing reasons.

Theres also a lifter that can lift a client into a Kleisli in http4s, so if you do that before the rest of your app you can still work in a fully abstract F, just pass in the client.

I extract and enhance my loggers at the point I know user identity and then pass those around with a ton of enhanced information to make debugging simple.
The very nice Gabriel Volpe test trading application creates using docker-compose some microservices to demonstrate distributed logging. However, to get the full use out of it, you need to set up a HoneyComb account to view them properly. (You also need to fudge the classpath if you want to Feed the cluster with some fake trades as it needs access to modules/domain/jvm/target/test-classes).

If you want to self-host, heed this advice:
hubt 
"Note that very few people run ES as a primary data store. it's notoriously difficult to manage reliably.
Almost everyone stores their primary data in something simpler(S3/GCP buckets) or more reliable(SQL DB). and then loads the data from the primary data store into ES." [Discord]



Sunday, May 7, 2023

The Joy of Sets?

The standard Scala Set violates basic algebra. Let's take two sets, x and y:

@ val x = Set(1, 2, 3) 
x: Set[Int] = Set(1, 2, 3)

@ val y = Set(3, 2, 1) 
y: Set[Int] = Set(3, 2, 1)

@ x == y 
res2: Boolean = true

If they're equal, we should be able to substitute one for another. Let's see how that goes:

@ def giveHead(s: Set[Int]): Int = s.head 
defined function giveHead

@ giveHead(x) 
res6: Int = 1

@ giveHead(y) 
res7: Int = 3

So, I get different results depending on which of two equal objects I call the method - crazy. 

topkek
Isn’t Map.toList impure or something like that?
tpolecat
It's pure but non-congruent with equality. m1 === m2 doesn't imply m1.toList === m2.toList because they could differ in their iteration order. That's why it's not Foldable. You can get those instances from alleycats I believe.
The unordered business is there to guarantee that Eq and Foldable are congruent (i.e., that a === b implies a.toList === b.toList) which is not necessarily true of data types like Set and Map: these are only UnorderedFoldable which requires you to fold into a commutative monoid which destroys any information you might get from observing iteration order. Which is to say "unordered" doesn't necessarily say anything about execution order, it just says any ordering that might be there won't be observable. 
Although often forgotten, sets are not functors.

You cannot parTraverse a Set (or Maps) in Cats. It just won't compile. This is a property of that particular data structure but it's not limited to Sets:
Fabio Labella @SystemFw Nov 16 16:55
You cannot traverse an [FS2] Stream, because it implies it's finite. Look at evalMap instead if the use case is "evaluate an action on each element". 
Instead, you must parUnorderedTraverse a Set.
Adam Rosien @arosien Nov 10 20:17
every Traverse is also an UnorderedTraverse, so they should act the same in that case (and since every CommutativeApplicative is also an Applicative, you can still call unorderedTraverse on something that has a Traverse)
To traverse a set (albeit unordered), the UnorderedTraverse will put the Set into a higher kinded type G[_]. If we want to take advantage of this say for List[_], we need a concrete implementation of a Parallel[List]. Among other things, this defines how to map from List[_] to Set[_] and back again. This brings us back to the problems with set's toList function - but at least now we're explicit in how we want this handled. 

Friday, June 24, 2022

Free Monads

Remember in a previous post that the Free monad can represent a tree where each node has either a branch or a label (thus inferring only leaves have labels).

When we "convert method calls into references to an ADT [we get] the Free monad. When an ADT mirrors the arguments of related functions, it is called a Church encoding. Free is named because it can be generated for free for any S[_] . For example, we could set S to be the [algebras] and generate a data structure representation of our program... In FP, an algebra takes the place of an interface in Java... This is the layer where we define all side-effecting interactions of our system." [1]

My friend, Aaron Pritzlaff, has written a small project that (apart from demonstrating interoperability) does not use any Cats nor Zio magic. 

Here, we build case classes that are all instances of Operation[A] (where the A represents the type of the operation's result). This can conveniently be lifted into a Free[F, A] (where F is now our Operation) with .liftM. 

As it happens, this creates a type of Free[ called a Suspend[ that's essentially a placeholder for the A as when we flatMap on it, the function we use is A => Free[F, A]. And we can keep doing this until we have built up a tree of Free[Operation, A]s as we promised at the beginning of this post. A shallow path represents a for comprehension that terminates early. 

The important thing to remeber is that we built a data structure that is yet to be executed.

We would execute the true with a call to ".foldMap [which] has a marketing buzzword name: MapReduce." [1] In Aaron's implementation, we just recursively call .foldMap on the Free tree we constructed. Either we'll terminate with a pure value or a Suspended node that will be mapped to a natural transformation (using the ~> function) that maps not A to B, but F[_] to G[_]. This is our interpreter and for us, G must be a monad so we can call flatMap as we pop the stack.

As we work our way back through the stack of monads calling flatMap as we go, we' re invoking those functions,  A => Free[F, A], we spoke about earlier. But note that they're acting on the Gs that our interpreter instantiated.

Although it's good to understand, the Free monad might not be your best choice according to the Cats people:

Rob Norris @tpolecat Sep 24 17:49 2020

Free is out of style but it's still great for some applications. Tagless style is much more common now for writing DSLs and APIs in general. Free is good if you really need to limit what the user can do (the algebra is exactly your data type's (i.e., F's) constructors plus Monad. No more, no less. Sometimes you want this.

The end result is easier to use than tagless because you're just dealing with data. You don't have to thread an effect parameter (typically) everywhere. So your business logic can be very concrete, which is easier for beginners I think.

Fabio Labella @SystemFw Sep 24 19:35 2020

the idea behind Free is great for implementing advanced monads like IO or Stream (which are implemented with a similar strategy even though they don't use Free literally)

Daniel Spiewak @djspiewak Sep 25 15:49 2020

I use Free for prototyping sometimes. It can be a useful tool to see how your effect algebra teases apart at a granular level without actually committing to an implementation, but it really only works for algebraic effects, and you very quickly bump up against some annoying type metaprogramming problems if you want to push it.

I think we've pretty much all learned that there are better ways of encoding effect composition than Free, and simultaneously the mocking it enables, while powerful, isn't that useful in practice... It's still a cool toy though.

[1] Functional Programming for Mortals with Cats


Sunday, November 21, 2021

Why set cannot be a functor

This is well known but keeps tripping me up when I think of an example so here it is.

Set demands that its elements implement Eq (or equals() or whatever is semantically equivalent). List has no such requirement. A bogus equals implementation will lead to Set violating the functor laws. 

Imagine an implementation that looked something like:

class OddEquals(val id: Int, val label: String) {
  override def equals(a: Any): Boolean =
    if (a.isInstanceOf[OddEquals]) {
      val other = a.asInstanceOf[OddEquals]
      id == other.id
    } else false

  override def hashCode(): Int = id
}

which would be a fairly typical piece of code for objects whose identity depends solely on their primary key.

Now, image two services. One finds an entity given any related string, we'll call it f. I'll fake it like this:

  type F = String => OddEquals
  val f: F = x => new OddEquals(x.length, x)

The other service simply extracts the entity's name:

  type G = OddEquals => String
  val g: G = _.label

The functor law says:

xs.map(x => g(f(x))) == xs.map(f).map(g))

But if xs is Set("hello", "world"), the left-hand side is the same as the input but the right-hand side is Set("hello").

Compare this to List("hello", "world") and you'll see that both left- and right-hand side are equal (that is, same as xs)

Monday, March 1, 2021

Exception, Exception, Exceptions

I asked this question in the Discord channel about ZIO 1.0.3:

If I have a ZIO[Any, Throwable, String] that just throws an Exception then I can handle the exception with ZIO.either. 

But if I have a ZIO[Any, Throwable, String] that throws an Exception in the release of bracket I get:

    Fiber failed.
    An unchecked error was produced.


Is that expected? [Code to demonstrate here on GitHub]

Adam Fraser responded:

@PhillHenry The release action of bracket should never fail so it has a type of URIO[R, Any]. If there is an error there you need to expose the full cause and either handle it or ignore it. 

You are kind of lying to the compiler by doing URIO(x.close()).

URIO promises that something will never fail but close can fail so that is going to create an unchecked exception. 

If you make that something like Task(x.close()) to reflect the fact that the effect can fail then you are going to get a compilation error when you try to put that in bracket. 

And then you have a choice as the user. If a finalizer fails, which really shouldn't happen, how do you want to treat it? One option is to say just fail at that point. Not successfully running the finalizer indicates a potential resource leak and better to fail fast than die slowly later. So then you would call orDie on the effect, and you could potentially handle the cause at a higher level of your application. Another option is to say we made our best efforts, we just need to move on, and then you would do ignore or maybe log the error somewhere and then ignore it.

Why couldn't the Exception manifest itself in the error channel of the ZIO?

Because the error channel models the failure of the acquire and use actions of bracket. It is possible that the use action fails and then the release action runs and also fails, so we need different channels for those errors.

Could ZIO not use Throwable.addSuppressed? I notice that this is what Java's try-with-resource does in these situations

Well we have to be polymotphic. The error of use may not be a Throwable at all so we definitely can't add a suppressed exception to that.

That also makes it really easy to lose failures in finalizers when normally a failure in a finalizer is very bad and something you should explicitly handle.

Well the only way you are cheating the compiler is by doing UIO(somethingThatThrows). I'm not sure how that can be prevented other than by preventing users from constructing UIO values at all, which prevents users from describing effects that really can't fail. 

I think the lesson is just to only use UIO for effects that really don't throw exceptions (or if they do throw exceptions you are comfortable treating those as defects).

Can we not force bracket to be available only if the error channel is a Throwable?
But we want to use bracket in a ton of situations where it is not.
The bracket operator is core to safe resource management. Saying we could only use it when E was Throwable would prevent us from writing resource safe code with a polymorphic error type, which is basically all code we want to write.

Disquieting

If a ZIO can throw any Exception to bring the whole machinary to a crashing halt, why have an error channel in the first place? This seems like ZIO is a leaky abstraction. And this is what bothers me although colleagues have told me they don't find it a worry at all.

The Cats way

Drew Boardman @drewboardman Feb 23 22:15
Basically I'm trying to see if there exists something like MonadError that signals whether the error has been handled but I think this is only possible with datatypes, not with typeclasses. I was having a discussion about how MonadError doesn't really signal, at the type-level, that anything has been handled. I basically got around to just creating a datatype that signals this - but that effectively just re-invents Either
Adam Rosien @arosien Feb 23 22:18
"whether the error has been handled" - do you mean knowing this statically? MonadError and the like don't distinguish, in the type, error-handling.
Fabio Labella @SystemFw Feb 23 22:18
It's kinda possible with cats-mtl , but in general yeah it's a lot easier with datatypes... So in this case you'd have a MonadError constraint, and handle it (eliminate it) by instantiating to EitherT (locally) and then you handle that. 
To recap, MonadError tells you that the called code has the right to raiseError and if it does, what type this will be. But there is no guarantee that an IO will not throw an Exception that brings the JVM crashing down. 

IOs that throw Exceptions  and MonadErrors that raiseError can be .attempted to get an IO[Either[. That is, the datatype not the effect indicates whether the exception has been handled or not.

Friday, January 1, 2021

Notes on Comonads

This excellent post by Eli Jordan can be summarized as comonads offer counit and cojoin, the opposites of on unit and join (or flatten as Cats calls it) monads. 

Why this is useful is that it can model a use case where there is the notion of focus. To demonstrate this, Jordan has created an implementation of Conway's Game of Life (code). Here, the focus is a cell which we determine is either on or off given its neighbours. (Another example of how comonads use focus can be found here where we traverse a graph, node by node).

The case class on which we will call counit and cojoin is an abstraction, Store[S, A], but for the Game of Life example, it is the grid on which the changing cells live. Notably, Store is instantiated with two arguments, 

  1. lookup: S => A
  2. index: S  

Specifically for our game, these represent:

  1. a mapping from a co-ordinate (S) to a Boolean (A) representing whether the corresponding cell is turned on or off.
  2. the co-ordinates of the cell that has the focus. That is, S for us is of type (Int, Int).

For each step in our game, we ask our grid (Store) what the neighbourhood is like (lookup) for its focused cell (index). 

We do this by calling its Cats coflatMap function (that we added via a type class) that takes a grid and returns a Boolean. 

This is where it gets interesting. You'll notice that Store has the potential for a partially applied constructor. That is, it looks like:

case class Store[S, A](lookup: S => A)(val index: S) { ...

So, if we instantiate Store with just a S => A, we actually have a S => Store[S, A].

In our Game of Life, this means that for the coflatMap branch of the code, the grid is not actually a mapping to whether the cell is turned on or off (F[Boolean]) but to the grid of the previous iteration on which we will apply Conway's rules to derive Boolean on/off  mappings. That is, it's our cojoin of type of F[F[Boolean]] that needs to be flattened.

Memoization

Recalculating Conway's rules recursively is expensive. Without memoization, you'll see deep stacks. Using jconsole, I saw quite a few:

- eliminated <owner is scalar replaced> (a Store) at Store.counit(Life.scala:69)

which apparently "represents a redundant lock that has been removed in the bytecode." [SO]. This is because the code on each lookup is trying to recalculate the grid from scatch. Memoization prevents this by caching the grid of the last iteration.

What triggers this recalculation is the rendering of the 20x20 grid. This grid obviously leads to 400 evaluations. But the grid (Store) of each iteration has a reference its predecessor's 400 on which it must calculate Conway's rules. Not only do we not want to recalculate that grid from the game's beginning, we don't want to perform a further 3600 (=400 * 8 neighbours) calculation for cells we've already evaluated - hence the caching.

Sunday, July 19, 2020

Cancellation idioms

Java IO interrupt refresher

"The InterruptibleChannel interface is a marker that, when implemented by a channel, indicates that the channel is interruptible... Most, but not all, channels are interruptible.

"Channels introduce some new behaviors related to closing and interrupts. If a channel implements the InterruptibleChannel interface, then it's subject to the following semantics. If a thread is blocked on a channel, and that thread is interrupted (by another thread calling the blocked thread's interrupt() method), the channel will be closed, and the blocked thread will be sent a ClosedByInterruptException.  Additionally, if a thread's interrupt status is set, and that thread attempts to access a channel, the channel will immediately be closed, and the same exception will be thrown." [Java NIO, Hitchens]

Summarising, if a thread is interrupted before or during a blocking call on a channel, the channel is closed and an exception is thrown.

"It may seem rather draconian to shut down a channel just because a thread sleeping on that channel was interrupted. But this is an explicit design decision made by the NIO architects.  Experience has shown that it's impossible to reliably handle interrupted I/O operations consistently across all operating systems."

"Interruptible channels are also asynchronously closable. A channel that implements InterruptibleChannel can be closed at any time, even if another thread is blocked waiting for an I/O to complete on that channel. When a channel is closed, any threads sleeping on that channel will be awakened and receive an AsynchronousCloseException. The channel will then be closed and will be no longer usable." [ibid]

The problems with Java

Daniel Spiewak @djspiewak Apr 24 19:47, 2020
There are a couple things with thread interruption that are horrible:

There's no way to build "uninterruptible" code. Meaning that you cannot have a critical section which acquires a resource atomically in several steps. Or in other words, there is no analogue to the acquire action in bracket.

The only way to detect self-cancelation for valid purposes (e.g. resource cleanup) is catching the InterruptedException, but doing this immediately flips the interrupted bit on the Thread back to false! 

The only solution to this is to do Thread.currentThread().interrupt() at the end of your exception handler, which almost no one knows to do. To make matters more annoying, even if you do this correctly, you mess up the stack trace on the interruption, because it's technically a new interrupt.
Oh, and exception handlers are not critical regions either, so if someone is just hammering the interrupt() button over and over externally, you could catch the exception, try to clean things up, and then get immediately interrupted again. This actually happens a lot because of the next point.

Catching Exception or Error will silently catch InterruptedException, even when that's almost guaranteed to not be what you want to do. This leads to silently ignoring interruption in most code paths, which is why people repeatedly hammer interrupt() in the first place.

The problems also go deeper than just Java but down to the OS level. "The underlying stream may not know it its closed until you attempt to write to it (e.g. if the other end of a socket closes it)" [SO]. "There's no API for determining whether a stream has been closed." [SO]

The semantics of cancelling

This is a complicated area. (see the interruption model proposed for Cats Effects 3 at https://github.com/typelevel/cats-effect/issues/681)
Basically interruptible/uninterruptible are not composable. The best way to think about it is that "interruptable means always accept the interrupt, no matter what", while "uninterruptible means always suppress the interrupt no matter what". But the uninterruptible(fa >> interruptible(fb) >> fc) breaks either one of the guarantees.
So you have to choose: do you want resource leaks (by biasing in favor of the innermost in that context), or do you want possible deadlocks (by biasing in favor of the outermost)?
And you can't even phrase it as inner/outer, because you can do the same thing in reverse: uninterruptible(interruptible(fa >> uninterruptible(fb) >> fc))
..
as prior art here, Haskell tried all of these and ultimately decided that mask/poll was the sanest solution [Daniel Spiewak, Gitter]

An alternative architecture

"it's a mistake to think of interruptibility as being an attribute of threads or fibers. Instead it should be an attribute of the activities which run on the threads/fibers, and of necessity, that means that any interruptible activity must have it's own first class interrupt channel. If we go down that route then an activity is interruptible if it 1) has an interrupt channel and 2) it's interrupt channel is accessible. If it doesn't have an interrupt channel, or the channel is hidden somehow, then it's uninterruptible.

"Exposing an explicit interrupt channel on every blocking operation that we want to be interruptible is obviously a lot more laborious than just firing random interrupts at globally visible threads/fibers and hoping for the best, but I think it's the only way to go."
[Miles Sabin]


How does this affect Effectful Systems?

Integrating Scala code that uses effectful libraries with Java IO code can cause problems.

Gavin Bisesi @Daenyth Feb 05 21:16, 2020
InputStream is always a blocking api
(note you don't need much to make a blocker; Blocker.apply gives a Resource of one)

Daniel Spiewak @djspiewak Feb 05 21:46, 2020
FYI, all things involving files are blocking except on Windows, and even then they're blocking most of the time.
So the "NIO stuff" that is inside of getResourceAsStream is actually not NIO but rather regular IO wrapped up with a thread pool :-(
I generally use Blocker just to be safe on resource access. It doesn't really cost that much in terms of syntax
... non-blocking things must have a callback-driven API
either directly (via callbacks passed to functions) or indirectly (via Future or CompletableFuture)
If something doesn't have a callback API, then you know it's blocking


When can you cancel?

The effect of a cancellation is felt at every asynch boundary or every N flatMaps (where N=1 for ZIO, it seems).

Note that there is no code in ZIO nor Cats that calls Thread.interrupt() that I could find. Note however that ZIO still gives you the ability to wrap your code in a Future and cancelling this will lead to an InterruptedException (see this gist).

Note that there are still undefined areas in Cats regarding cancellation:

Raas Ahsan @RaasAhsan Jul 07 20:26
calling cancel right after start results in non-deterministic behavior

Fabio Labella @SystemFw Jul 08 19:38
yeah I wanted to say
fa.guarantee(foo) doesn't guarantee that foo will always happen
it guarantees that if fa happens, then foo always happen
In particular if you have fa.guarantee(foo).start.flatMap(_.cancel), foo might happen or not, because the program can be cancelled before fa gets scheduled to run

See tip #2 at this Cats video (An Introduction to Interruption by Jakub Kozlowskiat 11'03" ) where starting and joining in a for comprehension is an anti-pattern (what if one fails to complete?). Instead, one should use (ioa, iob).parTupled.


Sunday, June 28, 2020

Error handling - the Cats way


Exceptions in the Java world suck as you don't necessarily know what will be "returned" by a method. Even if the exception is declared (and they often are not) then every method can "return" your expected value or maybe an Exception.  

So, how are they handled in the FP library, Cats?

Why is MonadError useful?

As ever, Rob Norris gives an immensely insightful answer on Gitter:

laiboonh @laiboonh Jun 27 14:44
Why can't I simply represent error with Either for example

Rob Norris @tpolecat Jun 27 14:45
It lets you abstract over things like Either.
 
Which is kind of a bad answer.
Some effects have an error channel. IO for instance. When you're talking to a database or something there are a billion things that can go wrong and you'll spend your whole life passing Eithers around, containing possible errors that you'll never be able to handle. So it's nicer to bury them in the effect itself and only summon them when you're in a position to do something about them.
 
Either can work this way. Your effect can be Either[MyError, ?]
But you can also have something like IO[Either[MyError, A]] which has an error channel for "bad" errors and an explicit Either for errors that you want the user to confront immediately. Kind of like runtime vs. checked exceptions in Java.
 
In any case MonadError lets you abstract over effects that have an error channel.
So you can write code that works with IO or Future or Try or Either or ...

@ def tryDiv[F[_]](a: Int, b: Int)(implicit ev: ApplicativeError[F, Throwable]): F[Int] =
    if (b != 0) ev.pure(a / b) else ev.raiseError(new ArithmeticException)
defined function tryDiv

@ tryDiv[IO](3, 1).unsafeRunSync
res4: Int = 3

@ tryDiv[IO](3, 0).unsafeRunSync
java.lang.ArithmeticException
  ammonite.$sess.cmd3$.tryDiv(cmd3.sc:2)
  ammonite.$sess.cmd5$.<clinit>(cmd5.sc:1)

@ tryDiv[Either[Throwable, ?]](3, 1)
res6: Either[Throwable, Int] = Right(3)

@ tryDiv[Either[Throwable, ?]](3, 0)
res7: Either[Throwable, Int] = Left(java.lang.ArithmeticException)

@ tryDiv[Try](3, 1)
res9: Try[Int] = Success(3)

@ tryDiv[Try](3, 0)
res10: Try[Int] = Failure(java.lang.ArithmeticException)


ApplicativeError and Option

You'll notice that Rob doesn't mention Option and there's a good reason for that. "This will not work for Option though : instances of these constructs do not exist for Options (the reason becomes obvious if you think 5 minutes about it)." [Reddit]

The answer for the impatient is because None carries no further information.  and "you will be throwing away some information and behaviour." [Ruben Pieters blog]

"I've played around with this a bit and it definitely breaks the existing laws for MonadError (and ApplicativeError) since the error value is always thrown away." [Cats Github issue]


Handling Errors

We can handle errors monadically avoiding the short-circuiting mechanics that you'd expect with a monad:

  val o1:     IO[Int] = IO(1)
  val o2:     IO[Int] = IO(2)
  val badBoy: IO[Int] = IO(1/0)

  val handledErrors: IO[Int] = for {
    a <- badBoy.handleErrorWith(_ => o1)
    b <- badBoy.orElse(o2)
  } yield a + b

Note that orElse is syntactic sugar and just defers to handleErrorWith. 

There's some nice error handling code in fs2-kafka:

(
        implicit F:      MonadError[F, Throwable],
                 jitter: Jitter[F],
                 timer:  Timer[F]
      )
...
        def retry(attempt: Int): Throwable => F[Unit] = {
          case retriable: RetriableCommitFailedException =>
            val commitWithRecovery = commit.handleErrorWith(retry(attempt + 1))
            if (attempt <= 10) backoff(attempt).flatMap(timer.sleep) >> commitWithRecovery
            else if (attempt <= 15) timer.sleep(10.seconds) >> commitWithRecovery
            else F.raiseError(CommitRecoveryException(attempt - 1, retriable, offsets))

          case nonRetriable: Throwable =>
            F.raiseError(nonRetriable)
        }

Nicely, this is stacking Fs like beads on a thread. Some Fs pause (calls to backoff return  F[_]s) and some Fs do the actual work of trying to commit (commit is a  F[Unit]). But whatever problems we face, we can call F.raiseError and no matter what F actually is, the appropriate semantics of its type will be used to return a pass or a fail.

Conclusion

As long as your effect has a type class of MonadError or ApplicativeError, you can raise an error. What the nature of this error is depends entirely on the effect.


Monday, April 13, 2020

Applicative and Effectful Types


Typical use case: we have several legacy java.io.OutputStreams to close after we have done some work. We want to try to close all of them even if some might fail to close. Can we encapsulate these individual units of work in monads?
Recall a key distinction between the type classes Applicative and Monad - Applicative captures the idea of independent computations, whereas Monad captures that of dependent computations. Put differently Applicatives cannot branch based on the value of an existing/prior computation. Therefore when using Applicatives, we must hand in all our data in one go. [Cats documentation]
So, not only is monad inappropriate, it's impossible to perform this use case with monads at all.

Ok, let's look more closely at these Applicatives:
Rob Norris @tpolecat There's already a common misconception that applicative composition implies order-independence, which isn't necessarily (or even commonly) true. I feel like we're still kind of struggling for a way to talk about effects. I am, anyway.
Fabio Labella @SystemFw Because most education resources state that Applicative does represent non ordered computation whereas it can represents non ordered computation.
Rob Norris @tpolecat Even Validated is order-dependent
[From Gitter]

My reading of this is that calling Apply's *> will "Compose two actions, discarding any value produced by the first." (Cats Apply Scaladoc). The order is important in that all but the last result is thrown away.

To demonstrate this, I wrote some code here [GitHub] that uses Validated from Cats which is an Applicative as its code [GitHub] proves here.

My code uses a filthy var but as even the esteemed Fabio Labella "you need a Ref, but not a Ref of aWriter, just a Ref with IO. You can also use a var, whether you use one or another often depends on how complex the test is (var works well for simple cases with no concurrency)". So, with that caveat, let's proceed.

      var i = 0
      def failure(): Validated[String, String] = {
        println("failure")
        i = i + 1
        Invalid("failure message")
      }
      def success(): Validated[String, String] = {
        println("valid")
        i = i + 1
        Valid("success message")
      }

      success() *> failure() *> success()
      i shouldBe 3

This passes. Now, let's look at a monadic version in the same test class:

    type     MonadType = Either[String, Int]
    val aye: MonadType = Right(1)
    val nay: MonadType = Left("nope")

    aye *> nay *> aye shouldBe nay

You can demonstrate if you like that the last aye is not actually called but this is left as an exercise.


ZIO

ZIO is another Effects system. It is totally independent of Cats (unlike Monix, yet another Effects system, which depends on Cats).
"A quick summary: efforts to use Pure Functional Programming in Scala began with Scalaz lib which was then continued by Cats. These libraries are great, but in essence they try to replicate the whole Haskell experience in Scala. This comes at a cost mainly because Haskell is a non-strict, lazy-by-default language while the JVM is both eager and strict, so in order to make some things work some techniques were used that affect your performance and Scala's ability to infer types.
ZIO strives to give you a PFP experience but taking adavantage of Scala's paradigms (such as variance) to help both with performance and type inference.
it also tries to be more friendly for newcomers" ToxicaFunk (12 April 2020) on Discourse
Now, although all monads are applicatives, not all applicatives are monads (cats.data.Validated being an example of such a type). "We sometimes use the terms monadic effects or applicative effects to mean types with an associated Monad or Applicative instance." [Functional Programming in Scala]

In ZIO, there is "one monad to rule them all", also called ZIO. So, how do you get Applicative behaviour? None other than ZIO's creator himself answered me on Discourse:
jdegoes 12 April 2020 at 2:10 PM
@PhillHenry x1.ignore &> x2.ignore &> x3. This will execute x1, x2, and x3 in parallel, using zipRightPar (that's the &> operator, zips two effects together in parallel, returning whatever is produced on the right), and ignore the result of x1 and x2 so their failures don't influence the result of the computation.
@PhillHenry In ZIO, even parallel zip or collect or foreach operations will "kill" the other running effects if one of them fails. Because that's often what you want. To get the other behavior, just use .ignore in the right places to ignore the failures you don't care about.
My equivalent ZIO code can be found here on GitHub. Looking at the thread names and stack traces, these operations do appear to execute on different thread/fibres. This is not the case on the Cats implementation.

Friday, March 13, 2020

What sorcery is this?


This library very clever. Imagine you want to listen on a TCP port. You represent that port number with an Int. But what if somebody misconfigures the value and you only realise the mistake at runtime? Wouldn't it be nice to catch it compile time? Now you can!

import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.numeric._

  type Port = Int Refined Interval.ClosedOpen[W.`1`.T, W.`65535`.T]

Is all we need to have compile time checking that an Int representing a port is in a sensible range. For instance, if we had:

  def listenOn(port: Port) = ??? 

then this works:

    listenOn(1: Port)

but these fail to compile:

    listenOn(0: Port)
    listenOn(65536: Port)

exactly as we expect and desire.

But it gets better. A common error in neural nets judging by the forums is not getting the size of matrices correct and only finding out at runtime after they have been running for a while. Wouldn't it be good to know at compile time? Now we can (code taken from this GitHub repository):


type ExactInt = Int Refined Equal[A] forSome { type A <: Int }

case class Matrix2[ROWS ExactInt, COLS  ExactInt](nRows: ROWS, nCols: COLS) {

  val REQUIRED_COLS =  Witness.mkWitness(nCols)

  def multiply[T ExactInt](x: Matrix2[REQUIRED_COLS.T, T]): Matrix2[ROWS, T] = Matrix2(nRows, x.nCols)

}


Note we don't actually do the multiplication - this is just a PoC and we're just interested in types.

Also note that this forSome is nicely explained by Rob Norris on his blog here where he deals with F-Bound Types (where an "F-bounded type is parameterized over its own subtypes") and uses forSome to enforce that.

Now we can create some matrices:

    val _3x7: Matrix2[Exactly[W.`3`.T], Exactly[W.`7`.T]]   = Matrix2(3: Exactly[W.`3`.T], 7: Exactly[W.`7`.T])
    val _7x8: Matrix2[Exactly[W.`7`.T], Exactly[W.`8`.T]]   = Matrix2(7: Exactly[W.`7`.T], 8: Exactly[W.`8`.T])
    val _8x7: Matrix2[Exactly[W.`8`.T], Exactly[W.`7`.T]]   = Matrix2(8: Exactly[W.`8`.T], 7: Exactly[W.`7`.T])

and this compiles:

    _3x7.multiply(_7x8)

(which is good because a 3x7 matrix can be multiplied by a 7x8 matrix) but this doesn't:

    _3x7.multiply(_8x7) // doesn't compile as expected

which makes sense as a 3x7 matrix cannot be multiplied by an 8x7 matrix. 

To do

I'm still constructing the matrix with both a type and a value that corresponds to that type. I've not yet worked out how I can just use the type and derive the value inside the matrix. If I work it out, I'll write another blog post.


Why FP?


In my last position, I was surprised by the number of programmers who were writing Java in the Scala language. That is, different language, same style and idioms. This to me misses the point of Scala. It is not just a better Java. In the realm of distributed computing, it's essential to fully embrace its functional programming aspects.

I gave a talk that was well-received by my colleagues and have since moved in. Unfortunately, the new shop is like the old where the developers are writing Java in Scala. So, these are the notes for my presentation that I may soon have to dust off and present again.

Intro

  • there are FP design patterns just like GoF in OO
  • Haskell and Scala programmers can talk in FP patterns just as fluently as Java and C++ programmers can talk in OO patterns
  • ground work for FP dates back to 1920s-1950s mathematics.
  • I’m assuming you all already know basic FP points (eg, immutability, don’t throw exceptions etc)
  • Disclaimer: I’m not the best FP programmer in the world and although I love Cats, am far from an expert.

Motivation

  • Make programming more mathematical using math proofs. This way, logic errors can be caught by the compiler rather than at runtime.
  • A common maths language allows us to more easily reason about the code (see Semigroups below).
  • Since everybody is singing to the same hymn sheet, code had a lot less boilerplate and is easier to reason about.

For-comprehensions

  • Advantage: calling and called code are decoupled regarding the ‘container’ type.
  • Advantage: which monad doesn’t matter (eg, Try, Either, Option, List etc).
  • Terminates early
  • The obvious structure for sequential operation.
  • Sequence: that is M[T[_]] => T[M[_]] can take a list of results and produce a result.

Which brings me to:

Semigroups

  • Useful in aggregation.
  • They’re associative operations on a type.
  • Question: is f(x1, f(x2, f(x3, x4))) associative for adding integers? Eg, is it the same as f(x1, f(f(x2, x3), x4)))?
  • Is it associative for subtracting integers?
  • It allows partitioning
  • If it’s also commutative, you can multi-thread the operations!

Applications to big data

  • Associativity allows partitioning
  • Commutativity allows maximum parralelism
  • Addition of real numbers is associative and commutative
  • Concatenating strings is associative but it’s not commutative
  • Substring real numbers is neither associative nor commutative

Disadvantages

  • If you use Cats, there are some odd imports and dependencies you need to get used to (not too hard)
  • You need to learn a new ‘pattern language’ but since it’s been about since the mid 20th Century, it’s not going to change overnight.
Conclusion

The following was not in my presentation but is just a great description of why we should prefer an FP approach:
"Consider a service which must read some data from shared state in order to proceed. The shared state is stored on other servers (e.g. Redis, Postgres, some other service, etc). State must be read from multiple of these sources in order to formulate a response. These services have varying latencies and availabilities, and you need to be failover each one individually to alternate primaries.

"Each read requires an asynchronous connect/read/close resource management process, its own retry and independent exponential backoff cycle, its own failover, etc. All of them must be run in parallel such that if any of them fails, they're all aborted as quickly as possible. Meanwhile, the client connection itself might get aborted, which should clean up all associated resources. In the event of any errors, or a success, the response must be returned to the client. All asynchronously.

"Doing the above without functional effects is astonishingly complex even with absolute best-in-class tools. Your best bet outside of IO is to use something like Future with very careful and prescribed use of cooperative explicit cancelation checks and extremely well thought-out refactoring into defs. It would be possible but brittle.

"Now imagine taking the above and adding an extra data source which needs to be aggregated to form a response. With FP this is trivial: you just write it independently and then add it to the parTraverse. With any other approach, even Futures, this is not really all that straightforward.

"To me, the above unequivocally demonstrates the value of functional programming in modern scalable service architectures" - Daniel Spiewak


Monday, January 20, 2020

FS2 Idioms (part 1)


The semantics of terminating

How do you know when a Stream has finished? Well, Stream.noneTerminate results in a Stream where items are wrapped in a Some and a None infers termination. Stream.unNoneTerminate does the opposite in that these wrapped items are unwrapped but terminates on the first None.

The semantics of pulling

There can be alternative behaviour when pulling from a Stream.

You could be guaranteed to get a value every time and if there is nothing new, you get the old value (Signal.continuous). In each case, the element comes in a new Stream.

Alternatively, every time you pull, you could get a new Stream that represents the latest update and if there is nothing new, the Stream is empty. This behaviour can be found in Signal.discrete.

Finally, you might want to get all the updates since you last pulled but in this case, use a `Queue` instead of Signal. This is because Signal is a "pure holder of a single value"

We can convert a Stream to a Signal by calling hold with a default value or holdOption to handle the None event.

Reading the signs

 
I had a bug in my code when I called this method on fs.concurrent.Topic:

  /**
    * Signal of current active subscribers.
    */
  def subscribers: Stream[F, Int]

My code seemed to hang. In desperation, I tried >> (which is basically flatMap { _ => ... } ) and although my Stream now seemed to start processing, the evaluated effects were not what I was expecting.

The ever helpful creator of FS2, Fabio Labella, helped me:
@PhillHenry the issue is that subscribers is an infinite stream
or rather, as long as the lifetime of the topic.
Each new element of the stream is a change in the number of subscribers
so subscribers ++ anything will never execute anything [but]
subscribers >> s2 will execute s2 every time there is a change in the number of subscribers.
If you look at the description of subscribers it says to return the current number of subscribers. If it were an F[Int], that it would be something you can poll: every time you call it, it gives you the current number and returns. But because the return type is a Stream, it means that it will emit every time there is a new subscriber, and that necessarily means it has to wait when there are no changes to the subscriber number. That is to say, it doesn't terminate.

It is not possible to know from the type whether a stream is infinite or not. In my case, if I wanted the current number of subscribers to the topic then I needed to call take(1). This way, I need not worry whether the stream is finite or not.

The take-away point is that in FP, the signatures are trying to help you to understand the semantics.

On the Pull with FS2


The learning curve for FS2 is steep and comparing it to Scala's built-in streams is not helpful. Given this Scala stream to generate Fibonacci numbers:

  val f: Stream[Int] = 0  #:: 1  #:: f.zip(f.tail).map { case (x, y) => x + y }

you might think you can do this:

    val spliced = f.take(5) ++ f.drop(5)

and treat spliced as normal:

    println(spliced.take(10).mkString(", ")) // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

And you'd be correct when working with Scala streams but not for FS2 streams, that is s != s.take(n) ++ s.drop(n)
Fabio Labella @SystemFw
I mean, this is certainly not the case in general, since the RHS reevaluates s twice
That is, s.take(n) ++ s.drop(n) will return the same values as Scala's streams but evaluate different effects to what you might expect.

For example, if we had a stream:

    rangeStream(6).evalMap { x =>
      IO {
        println(s"x = $x")
        x
      }
    }

then s.take(3) ++ s.drop(3) would indeed return a Stream containing 1,2,3,4,5,6  but you'd see this printed out:

x = 1
x = 2
x = 3
x = 1
x = 2
x = 3
x = 4
x = 5
x = 6

As Fabio says "you can use Pull if you want 'take some, then give me the rest' semantics". So, this is what I did here on GitHub.

Aside: the reason I want to splice an FS2 Stream is to introduce some test assertions. Here, I want the opposite of the above, namely to evaluate the effect (my assertion in this case) but to ignore the value. Here, one can use

Stream.eval_
 
Note the underscore. However, be a bit careful here as if there is no value returned, calls like myStream.take may semantically block.

Sunday, December 1, 2019

Monad Transformers: code smell in disguise?


What are they?

Luka Jacobowitz (Gitter @LukaJCB 01/04/20 01:48) says "the definition of monad transformer is that for any MT[F, A] if F is a monad than MT[F, *] is also a monad."

Noel  Welsh gives us an example of a 3-way monad: None, Some(x) or a (String) error:
Monad transformers allow us to stack monads. Eg, stacking an Option with \/ (that is, Scalaz's version of Either) into one handy monad. The type OptionT[M[_], A] is a monad transformer that constructs an Option[A] inside the monad M. So the first important point is the monad transformers are built from the inside out.
Here's an example:

  def transformAndAdd[F[_]: Monad](fa: OptionT[F, Int], fb: OptionT[F, Int]): OptionT[F, Int] = for {
    a <- fa
    b <- fb
  } yield a + b


Here, F can be any monad. For instance:

    val faIO = OptionT[IO, Int](IO(Some(1)))
    val fbIO = OptionT[IO, Int](IO(Some(2)))

    transformAndAdd(faIO, fbIO)

will wrap the result of the addition in an IO. You can do something similar for Future:

    val fa = OptionT[Future, Int](Future(Some(1)))
    val fb = OptionT[Future, Int](Future(Some(2)))

    println(Await.result(transformAndAdd(fa, fb).value, 1.seconds))

We care not what the outer monad is as long as the inner is an Option in this case.

Drawbacks

Not all combinations of monads can be used with transformers. This SO answer explains why:
Important thing here is that there is no FutureT defined in cats, so you can compose Future[Option[T]], but can't do that with Option[Future[T]] ... 
That means you can compose Future x Option (aka Option[Future[T]]) to one single monad (coz OptionT exists), but you can't compose Option x Future (aka Future[Option[T]]) without knowing that Future is something else besides being a monad (even though they’re inherently applicative functors - applicative is not enough to neither build a monad nor monad transformer on it)
The omniscient Rob Norris explains why:
Rob Norris @tpolecat
Like Option[IO. There's no IOT transformer. 
PhillHenry @PhillHenry
Because nobody has written one? Or it can't be done?
(Sorry for such a silly question. Friday night here.) 
Rob Norris @tpolecat
It can't be done. You would have to define Option[IO[A]] => (A => Option[IO[B]]) => Option[IO[B]] but there's no way to do that because you can't get the A out.

Should we even use them?

There appear to be two schools of thought on Monad Transformers. People aligned to Scalaz appear to hate them in Scala.
jrciii @jrciii
Are monad transformer stacks idiomatic scalaz? If not is there a better way to get either an error or a value with logging along the way? 
beezee @beezee
eh idiomatic i have to defer to others. i feel like the idioms change every time there's a conference
i used them a lot and now consider that code legacy
i dream of the day it's all gone
it seems very in fashion to mimic mtl
scalaz has MonadError and MonadWriter
the nice thing about it is you don't worry about plumbing in your business code and you can be explicit about what capabilities are required for every function
i'm sure others who have used it more than me could describe pitfalls there too 
John A. De Goes @jdegoes
Monad transformers are not idiomatic in Scala. Avoid them at all costs. [See John's article here]

Emily Pillmore
The real question you need to ask after deciding Monad Transformers are not good in Scala is why that is true. The answer is not that MT are bad, per se, but that Scala has very poor facilities for dealing with nested boxed and unboxed values alike. It simply is not a language that has reasonable data formats 
John A. De Goes @jdegoes
@PhillHenry Yeah, what Emily says. Monad transformers work well in Haskell (actually way more performant than, e.g. free monads). But Scala is not Haskell. In Scala, monad transformers destroy performance and type inference, and add tedious boilerplate unless used in combination with type classes (in which case you just have to worry about type inference and performance).

[Gitter chat, 20 & 26 Feb  2019]

But the Cats crowd don't seem to mind:
Rob Norris @tpolecat
The correct take on monad transformers is that they're fine. Write your program in terms of abstract effect F and then at the end of the world if you have to instantiate it into a transformer stack, who cares? It's awkward but just for a line or two. 
Gavin Bisesi @Daenyth
And unless your app's runtime does no database or network IO, any performance overhead from using transformers is just not worth caring about. IO drowns it out by orders of magnitude

[Gitter chat, 1 Nov 2019]

But even the Cats people say: "Strong claim: Thou shalt not use Monad Transformers in your interface" (Practical FP in Scala by Gabriel Volpe)