Quantcast
Channel: Typelevel
Viewing all 285 articles
Browse latest View live

There are more types than classes

$
0
0

As programmers, we are very incautious with our use of the word “type”. The concept of “type” is sufficiently abstract and specific that we are tempted to understand it by analogy, so much that we begin to confuse analogy with sameness.

The colloquial “runtime type”, a fair approximation of “class”, makes it tempting to equate types with “classes, interfaces, traits, that sort of thing”, which I will name classes for the rest of this article. But they aren’t the same. The type system is much richer and more interesting than the class system, even in Java.

To appreciate this richness, we must stop thinking of types as classes and stop drawing conclusions from that weak analogy. Luckily, the compiler will readily reveal how unlike classes types are, if we ask it some simple questions.

One value with class, many variables with type

val greeting: String = "hi there!"

Here I have constructed a String and assigned it to a variable. (I have also constructed the char array in the String and various other details, but immediately handed those off to the String and forgotten about them.) This value has class String. It has several classes, really.

  1. String
  2. java.io.Serializable
  3. CharSequence
  4. Comparable[String]
  5. Object/AnyRef

That seems like a lot of classes for one value. And they are genuine classes of greeting, though 2-5 are all implied by #1.

greeting also has all five of these types. We can ask the compiler to verify that this type truth holds, entirely separately from the class truth.

scala> (greeting: String, greeting: java.io.Serializable,
        greeting: CharSequence, greeting: Comparable[String],
        greeting: AnyRef)
res3: (String, java.io.Serializable, CharSequence,
       Comparable[String], AnyRef) =
  (hi there!,hi there!,hi there!,hi there!,hi there!)

So we have exhausted the classes, but aren’t quite done with types.

scala> greeting: greeting.type
res0: greeting.type = hi there!

greeting.type is not like the other five types we just tested. It is a strict subtype of String, and has no class with the same name.

// If and only if call compiles, A is a subtype of B.
def conformance[A, B >: A]: Unit = ()

scala> conformance[greeting.type, String]

scala> conformance[String, greeting.type]
<console>:14: error: type arguments [String,greeting.type] do not conform
              to method conformance's type parameter bounds [A,B >: A]

Fine, we can accept that object identity is represented at the type level without our universe imploding, by inventing the theory that this is about object identity; hold on, though:

scala> val salutation = greeting
salutation: String = hi there!

Fine, salutation is just another name for greeting, right?

scala> conformance[salutation.type, String]

scala> implicitly[greeting.type =:= salutation.type]
<console>:14: error: Cannot prove that greeting.type =:= salutation.type.

Now we have seven. I’ll spare you spelling out the induction: each new variable defined like salutation will yield a new alias with a distinct type. This is not about objects; this is about variables!

// find a type for the literal "hi there!"
scala> val literalHiThere = shapeless.Witness("hi there!")
literalHiThere: shapeless.Witness.Aux[String("hi there!")] = shapeless.Witness$$anon$1@1d1537bb

scala> conformance[greeting.type, literalHiThere.T]
<console>:15: error: type arguments [greeting.type,literalHiThere.T] do not conform
              to method conformance's type parameter bounds [A,B >: A]

scala> conformance[literalHiThere.T, greeting.type]
<console>:15: error: type arguments [literalHiThere.T,greeting.type] do not conform
              to method conformance's type parameter bounds [A,B >: A]

As local variables are a strictly compile-time abstraction, and we have anyway seen that the numbers don’t match up, that should be the end of the “types are classes” confusion for you. But maybe this is just some Scala oddity! And anyhow I haven’t even begun to demonstrate the overwhelming richness of the type model as it blindingly outshines the paucity of the class model. Let’s go further.

No values, infinite types: method type parameters

To our small program of a greeting, we can add a small method.

def pickGreeting[G](grt: G, rand: Int) = grt

scala> pickGreeting(greeting, 42)
res9: String = hi there!

It seems like G must be String, because the argument passed to pickGreeting is a string, and in that case so must its return value be, according to the implementation. And from the perspective of this call, outside pickGreeting’s implementation, it is String indeed.

But that implementation’s perspective matters, too; it is also part of our program. And it sees things quite differently. We can ask its thoughts on the matter by adding to its body

def pickGreeting[G](grt: G, rand: Int) = {
  implicitly[G =:= String]
  grt
}

<console>:12: error: Cannot prove that G =:= String.
         implicitly[G =:= String]
                   ^

In fact, G bears no direct relationship to String at all.

// replace implicitly with
conformance[G, String]

<console>:13: error: type arguments [G,String] do not conform
              to method conformance's type parameter bounds [A,B >: A]
         conformance[G, String]
                    ^

// or with
conformance[String, G]

<console>:13: error: type arguments [String,G] do not conform
              to method conformance's type parameter bounds [A,B >: A]
         conformance[String, G]
                    ^

Let’s apply the pigeonhole principle. Imagine that you had a list of every class that ever was or ever will be. Imagine that, somehow, all of these classes, from String to AbstractFactoryMethodProxyBuilder, were on your classpath, available to your program.

Next, imagine that you had the time and inclination to try the =:= test with every last one of these classes.

implicitly[G =:= javax.swing.JFrame]
implicitly[G =:= AbstractFactoryMethodProxyBuilder]
// ad [in]finitum

Your search will be futile; every class on your list-of-every-class will give the same compiler error we got with String.

So, since G is not equal to anything on this list, it must be something else that doesn’t appear on the list. Because this list contains all classes, G must be something other than a class.

It must not necessarily be anything

It seems like it might be convenient to say “well, in this program G is only ever String by substitution, so therefore it is, even if the compiler doesn’t see that.” However, thinking like this misses out on the second key advantage of type parameterization, the one not based on multiplicity of substitution, or the type-safety of callers: blindness.

The implementation of type-parameterized classes and methods are required to treat each type parameter uniquely, uniformly, and without prejudice. The compiler enforces this by making the implementation blind to what that parameter, like G, could be. It can only use what the caller, the “outside”, has told it about G—arguments whose types contain G, like List[G], (G, G) => G, or G itself, like the argument to pickGreeting. This is information-hiding at the type level; if you find information-hiding a useful tool for implementing correct programs, you will find the same of the fresh, unique, and mysterious types induced by each introduction of a type parameter.

Each operation a language permits by default, not via an argument, on values of a type parameter is a leak in this abstraction. This includes testing the value’s class, converting to string, and comparing to other values of supposedly utter mystery for equality. The ability to create a “default” value is also a leak. A function is always permitted to ask only that of these that it needs from the caller; make them default, and this design choice is taken away. That is why Object#equals is little better for type-safety than reflection-based calls, and why total type erasure is a desirable feature rather than a design flaw—plugging these leaks gives the programmer as much freedom to abstract by information-hiding as she wishes.

How many calls are there?

Put another way, when implementing the code in the scope of a type parameter, your implementation must be equally valid for all possible G substitutions, including the ones that haven’t been invented yet. This is why we call it universal quantification.

But it is not merely each declaration of a type parameter that yields a distinct type—each call does! Consider two consecutive calls to pickGreeting.

pickGreeting(greeting, 42)
pickGreeting(33, 84)

Externally, there are two G types. However, the possibility of writing this demands another level of uniqueness treatment when typechecking pickGreeting’s definition: whatever G is now, like String, it might be something else in the next call, like Int in the above example. With recursion, it might even be two different things at the same time. There’s nothing to hold this at two, either: there may be an unbounded number of substitutions for a given type parameter within a single program, at a single point in time.

While G may be the same between two invocations of pickGreeting, it might not. So we have no choice but to treat the G types of each call as separate types. There may be infinitely many calls, so there are so many types.

Incidentally, the same happens for singleton types. Each time val greeting comes into scope, it induces a separate singleton type. It is easy enough to arrange for an unbounded number of scope entries in a particular program. This isn’t so practical as the type parameter phenomenon, though.

More types from variable copies

Suppose we’d like to wait a while to compute our greeting. We can define a type-and-class to represent that conveniently.

// like Coyoneda Id, if that helps
sealed abstract class Later[A] {
  type I
  val i: I
  val f: I => A
}

def later[In, A](now: In)(later: In => A)
  : Later[A] = new Later[A] {
    type I = In
    val i = now
    val f = later
}

val greeting3 = later(3){
  n => List.fill(n)("hi").mkString(" ")
}

How many classes are involved here, in the type of greeting3?

  1. Later, obviously;
  2. Function1, the greeting3.f overall class;
  3. String, the output type of greeting3.f;
  4. Int, the I type.

How many types?

The first difference is that greeting3.I is not Int.

scala> implicitly[greeting3.I =:= Int]
<console>:14: error: Cannot prove that greeting3.I =:= Int.
       implicitly[greeting3.I =:= Int]
                 ^

They are unrelated for much the same reason as G was unrelated to String in the previous example: the only things code following val greeting3 may know are those embodied in the greeting3.i and greeting3.f members. You can almost think of them as “arguments”.

But that’s not all.

val salut3 = greeting3

scala> greeting3.f(greeting3.i)
res11: String = hi hi hi

scala> salut3.f(salut3.i)
res12: String = hi hi hi

scala> greeting3.f(salut3.i)
<console>:14: error: type mismatch;
 found   : salut3.i.type (with underlying type salut3.I)
 required: greeting3.I
       greeting3.f(salut3.i)
                          ^

scala> implicitly[greeting3.I =:= salut3.I]
<console>:14: error: Cannot prove that greeting3.I =:= salut3.I.

Just like every call to pickGreeting induces a new G type, each simple val copy of greeting3 will induce a new, unique I type. It doesn’t matter that they’re all the same value; this is a matter of variables, not values, just as with singleton types.

But that’s still not all.

One value with class, many variable references with types

The preceding is more delicate than it seems.

var allo = greeting3

scala> allo.f(allo.i)
<console>:13: error: type mismatch;
 found   : Later[String]#I
 required: _1.I where val _1: Later[String]
       allo.f(allo.i)
                   ^

All we have done differently is use a mutable var instead of an immutable val. Why is this enough to throw a wrench in the works?

Suppose you had another value of the Later[String] type.

val bhello = later("olleh")(_.reverse)

The I substitution here is String. So the f takes a String argument, and the I is a String.

bhello is of a compatible type with the allo var. So this assignment will work.

allo = bhello

In a sense, when this mutation occurs, the I type also mutates, from Int to String. But that isn’t quite right; types cannot mutate.

Suppose that this assignment happened in the middle of that line of code that could not compile. We could imagine the sequence of events, were it permitted.

  1. allo.f (which is greeting3.f) evaluates. It is the function (n: Int) => List.fill(n)("hi").mkString(" ").
  2. The allo = bhello assignment occurs.
  3. allo.i (which is bhello.i) evaluates. It is the string "olleh".
  4. We attempt to pass "olleh" as the (n: Int) argument to complete the evaluation, and get stuck.

Just as it makes no difference what concrete substitutions you make for G, it makes no difference whether such an assignment could ever happen in your specific program; the compiler takes it as a possibility because you declared a var. (def allo = greeting3 gets the same treatment, lest you think non-functional programs get to have all the fun here.) Each reference to allo gets a new I type member. That failing line of code had two allo references, so was working with two incompatible I types.

Since the number of references to a variable in a program is also unbounded…you get the picture.

This also occurs with existential type parameters, which are equally expressive to type members. Accordingly, Java also generates new types from occurrences of expressions of existential type.

How do we tell the two apart?

All of this is simply to say that we must be working with two separate concepts here.

  1. The runtime shape and properties of the values that end up flying around when a program actually runs. This we call class.
  2. The compile-time, statically-discoverable shape and properties of the expressions that fly around when a program is written. This we call type.

The case with var is revealing. Maybe the I type will always be the same for a given mutable variable. But demonstrating that this holds true for one run of the program (#1, class) isn’t nearly good enough to prove that it will be true for all runs of the program (#2, type).

We refuse to apply the term “type” to the #1, ‘class’ concept because it does not live up to the name. The statement “these two types are the same” is another level of power entirely; “these two values have the same class” is extraordinarily weak by comparison.

It is tempting to use the term “runtime type” to refer to classes. However, in the case of Scala, as with all type systems featuring parametric polymorphism, classes are so dissimilar to types that the similar-sounding term leads to false intuition, not helpful analogy. It is a detriment to learning, not an aid.

Types are compile-time, and classes are runtime.

When are types real?

The phase separation—compile-time versus runtime—is the key to the strength of types in Scala and similar type systems. The static nature of types means that the truths they represent must be universally quantified—true in all possible cases, not just some test cases.

We need this strength because the phase separation forbids us from taking into account anything that cannot be known about the program without running it. We need to think in terms of “could happen”, not “pretty sure it doesn’t”.

How do classes give rise to types?

There appears to be some overlap between the classes of greeting and its types. While greeting has the class String, it also has the type String.

We want types to represent static truths about the expressions in a program. That’s why it makes sense to include a “model of the classes” in the type system. When we define a class, we also define an associated type or family of types.

When we use a class to construct a value, as in new Blob, we would like to assign as much specific meaning to that expression as we can at compile time. So, because we know right now that this expression will make a value of class Blob, we assign it the type Blob too.

How do the types disappear?

There’s a common way to throw away type information in Scala, especially popular in object-oriented style.

val absGreeting: CharSequence = greeting

absGreeting has the same value as greeting, so it has the same five classes. However, it only has two of those five types, because we threw away the other three statically. It has lost some other types, too, namely greeting.type, and acquired some new ones, namely absGreeting.type.

Once a value is constructed, the expression will naturally cast off the types specifying its precise identity, as it moves into more abstract contexts. Ironically, the best way to preserve that information as it passes through abstract contexts is to take advantage of purely abstract types—type parameters and type members.

scala> pickGreeting[greeting.type](greeting, 100)
res16: greeting.type = hi there!

While the implementation must treat its argument as being of the abstract type G, the caller knows that the more specific greeting.type must come out of that process.

How do the types come back?

There is a feature in Scala that lets you use class to get back some type information via a dynamic, runtime test.

absGreeting match {
  case hiAgain: String =>
    conformance[hiAgain.type, String] // will compile
}

The name “type test” for this feature is poorly chosen. The conclusion affects the type level—hiAgain is, indeed, proven statically to be of type String—but the test occurs only at the class level.

The compiler will tell you about this limitation sometimes.

def pickGreeting2[G](grt: G, rand: Int): G =
  ("magic": Any) match {
    case ok: G => ok
    case _ => sys error "failed!"
  }

<console>:13: warning: abstract type pattern G is unchecked
              since it is eliminated by erasure
           case ok: G => ok
                    ^

But reflecting the runtime classes back to compile-time types is a subtle art, and the compiler often can’t explain exactly what you got wrong.

def pickGreeting3[G](grt: G, rand: Int): G =
  grt match {
    case _: String =>
      "Surely type G is String, right?"
    case _ => grt
  }

<console>:14: error: type mismatch;
 found   : String("Surely type G is String, right?")
 required: G
             "Surely type G is String, right?"
             ^

I’ve touched upon this mistake in previous articles, but it’s worth taking at least one more look. Let’s examine how tempting this mistake is.

String is a final class. So it is true that G can contain no more specific class than String, if the first case matches. For example, given trait MyAwesomeMixin, G cannot be String with MyAwesomeMixin if this case succeeds, because that can’t be instantiated; you would need to create a subclass of String that implemented MyAwesomeMixin.

This pattern match isn’t enough evidence to say that G is exactly String. There are still other class-based types it could be, like Serializable.

pickGreeting3[java.io.Serializable](greeting, 4055)

Instead, it feels like this pattern match confirms Serializable as a possibility, instead of denying it.

But we don’t need G = String for this code to compile; we only need G >: String. If that was true, then "Surely type G is String, right?", a String, could simply upcast to G.

However, even G >: String is unproven. There are no subclasses of String, but there are infinitely many subtypes of String. Including the G created by each entry into pickGreeting3, every abstract and existential type bounded by String, and every singleton type of String variable definitions.

This mistake is, once again, confusing a demonstration of one case with a proof. Pattern matching tells us a great deal about one value, the grt argument, but very little about the type G. All we know for sure is that “grt is of type G, and also of type String, so these types overlap by at least one value.” In the type system, if you don’t know something for sure, you don’t know it at all.

Classes are a concrete source of values

In the parlance of functional Scala, concrete classes are often called “data constructors”.

When you are creating a value, you must ultimately be concrete about its class, at the bottom of all the abstractions and indirections used to hide this potentially messy detail.

scala> def pickGreeting4[G]: G = new G
<console>:12: error: class type required but G found
       def pickGreeting4[G]: G = new G
                                     ^

You’ll have to do something else here, like take an argument () => G, to let pickGreeting4 construct Gs.

The truly essential role that classes play is that they encapsulate instructions for constructing concrete values of various types. In a safe program, this is the only feature of classes you’ll use.

In Scala, classes leave fingerprints on the values that they construct, without fail. This is merely an auxiliary part of their primary function as value factories, like a “Made in class Blah” sticker on the back. Pattern matching’s “type tests” work by checking this fingerprint of construction.

Most runtime “type test” mechanisms do not work for types

These fingerprints only come from classes, not types. So “type tests” only work for “classy” types, like String and MyAwesomeMixin. They also work for specific singleton types because construction also leaves an “object identity” fingerprint that the test can use.

The ClassTag typeclass does not change this restriction. When you add a ClassTag or TypeTag context bound, you also prevent that type parameter from working with most types.

scala> implicitly[reflect.ClassTag[greeting3.I]]
<console>:14: error: No ClassTag available for greeting3.I
       implicitly[reflect.ClassTag[greeting3.I]]
                 ^

As such, judicious use of ClassTag is not a great solution to excessive use of type tests in abstract contexts. There are so many more types than classes that this is to confine the expressivity of your types to a very small, class-reflective box. Set them free!

“But doesn’t Python/JavaScript/&c have both types and classes at runtime?”

In JavaScript, there’s a very general runtime classification of values called “type”, meant to classify built-in categories like string, number, and the like.

>> typeof "hi"
"string"
>> typeof 42
"number"
>>> typeof [1, 'a']
"object"

Defining a class with the new class keyword doesn’t extend this partition with new “types”; instead, it further subdivides one of those with a separate classification.

>> class Foo() {}
>> class Bar() {}
>> typeof (new Foo)
"object"
>> typeof (new Bar)
"object"
>> new Foo().constructor
function Foo()
>> new Bar().constructor
function Bar()

So, if you treat JavaScript’s definition of the word “type” as analogous to the usage in this article, then yes, JavaScript has “runtime types”.

But JavaScript can only conveniently get away with this because its static types are uninteresting. It has one type—the type of all values—and no opportunities to do interesting type-level modeling, at least not as part of the standard language.

Hence, JavaScript is free to repurpose the word “type” for a flavor of its classes, because our “types” aren’t a tool you make much use of in JavaScript. But when you come back to Scala, Haskell, the ML family, et al, you need a word for the static concept once again.

Thinking about types as just classes leads to incorrect conclusions

Setting aside the goal of principled definition of terms, this separation is the one that makes the most sense for a practitioner of Scala. Consider the practicalities:

Types and classes have different behavior, are equal and unequal according to different rules, and there are a lot more types than classes. So we need different words to distinguish them.

Saying “compile-time type” or “runtime type” is not a practical solution—no one wants to speak such an unwieldy qualifier every time they refer to such a commonly-used concept.

While I’ve given a sampling of the richness of the type system in this article, it’s not necessary to know that full richness to appreciate or remember the difference between the two: types are static and compile-time; classes are dynamic and runtime.

This article was tested with Scala 2.12.1, Shapeless 2.3.2, and Firefox 53.0a2.

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.


Four ways to escape a cake

$
0
0

The mixin style of importing in which classes and traits are defined within traits, as seen in scala.reflect.Universe, ScalaTest, and other Scala styles, seems to be infectious. By that, I mean once you define something in a trait to be mixed in, to produce another reusable module that calls that thing, you must define another trait, and so must anyone using your module, and so on and so forth. You effectively become “trapped in the cake”.

However, we can use type parameters that represent singleton types to write functions that are polymorphic over these “cakes”, without being defined as part of them or mixed in themselves. For example, you can use this to write functions that operate on elements of a reflection universe, without necessarily passing that universe around all over the place.

Well, for the most part. Let’s see how far this goes.

Our little universe

Let’s set aside the heavyweight real-world examples I mentioned above in favor of a small example. Then, we should be able to explore the possibilities in this simpler space.

final case class LittleUniverse() {
  val haystack: Haystack = Haystack()

  final case class Haystack() {
    def init: Needle = Needle()
    def iter(n: Needle): Needle = n
  }

  final case class Needle()
}

For brevity, I’ve defined member classes, but this article equally applies if you are using abstract types instead, as any Functional programmer of pure, virtuous heart ought to!

Suppose we have a universe.

scala> val lu: LittleUniverse = LittleUniverse()
lu: LittleUniverse = LittleUniverse()

The thing that Scala does for us is not let Haystacks and Needle s from one universe be confused with those from another.

val anotherU = LittleUniverse()

scala> lu.haystack.iter(anotherU.haystack.init)
<console>:14: error: type mismatch;
 found   : anotherU.Needle
 required: lu.Needle
       lu.haystack.iter(anotherU.haystack.init)
                                          ^

The meaning of this error is “you can’t use one universe’s Haystack to iter a Needle from another universe”.

This doesn’t look very important given the above code, but it’s a real boon to more complex scenarios. You can set up a lot of interdependent abstract invariants, verify them all, and have the whole set represented with the “index” formed by the singleton type, here lu.type or anotherU.type.

Working with a universe on hand

Refactoring in macro-writing style seems to be based upon passing the universe around everywhere. We can do that.

def twoInits(u: LittleUniverse): (u.Needle, u.Needle) =
  (u.haystack.init, u.haystack.init)

def stepTwice(u: LittleUniverse)(n: u.Needle): u.Needle =
  u.haystack.iter(u.haystack.iter(n))

The most important feature we’re reaching for with these fancy dependent method types, and the one that we have to keep reaching for if we want to write sane functions outside the cake, is preserving the singleton type index.

scala> twoInits(lu)
res3: (lu.Needle, lu.Needle) = (Needle(),Needle())

scala> stepTwice(anotherU)(anotherU.haystack.init)
res4: anotherU.Needle = Needle()

These values are ready for continued itering, or whatever else you’ve come up with, in the confines of their respective universes. That’s because they’ve “remembered” where they came from.

By contrast, consider a simple replacement of the path-dependencies with a type projection.

def brokenTwoInits(u: LittleUniverse)
    : (LittleUniverse#Needle, LittleUniverse#Needle) =
  (u.haystack.init, u.haystack.init)

scala> val bti = brokenTwoInits(lu)
bti: (LittleUniverse#Needle, LittleUniverse#Needle) = (Needle(),Needle())

That seems to be okay, until it’s time to actually use the result.

scala> lu.haystack.iter(bti._1)
<console>:14: error: type mismatch;
 found   : LittleUniverse#Needle
 required: lu.Needle
       lu.haystack.iter(bti._1)
                            ^

The return type of brokenTwoInits “forgot” the index, lu.type.

Getting two needles without a universe

When we pass a LittleUniverse to the above functions, we’re also kind of passing in a constraint on the singleton type created by the argument variable. That’s how we know that the returned u.Needle is a perfectly acceptable lu.Needle in the caller scope, when we pass lu as the universe.

However, as the contents of a universe become more complex, there are many more interactions that need not involve a universe at all, at least not directly.

def twoInitsFromAHaystack[U <: LittleUniverse](
    h: U#Haystack): (U#Needle, U#Needle) =
  (h.init, h.init)

scala> val tifah = twoInitsFromAHaystack[lu.type](lu.haystack)
tifah: (lu.Needle, lu.Needle) = (Needle(),Needle())

Since we didn’t pass in lu, how did it know that the returned Needles were lu.Needles?

  1. The type of lu.haystack is lu.Haystack.
  2. That type is shorthand for lu.type#Haystack.
  3. We passed in U = lu.type, and our argument meets the resulting requirement for a lu.type#Haystack (after expanding U).
  4. The type of the expression h.init is u.Needle forSome {val u: U}. We use an existential because the relevant variable (and its singleton type) is not in scope.
  5. This type widens to U#Needle, satisfying the expected return type.

This seems like a more complicated way of doing things, but it’s very freeing: by not being forced to necessarily pass the universe around everywhere, you’ve managed to escape the cake’s clutches much more thoroughly. You can also write syntax enrichments on various members of the universe that don’t need to talk about the universe’s value, just its singleton type.

Unless, you know, the index appears in contravariant position.

Syntactic stepTwice

One test of how well we’ve managed to escape the cake is to be able to write enrichments that deal with the universe. This is a little tricky, but quite doable if you have the universe’s value.

With the advent of implicit class, this became a little easier to do wrongly, but it’s a good start.

implicit class NonWorkingStepTwice(val u: LittleUniverse) {
  def stepTwiceOops(n: u.Needle): u.Needle =
    u.haystack.iter(u.haystack.iter(n))
}

That compiles okay, but seemingly can’t actually be used!

scala> lu stepTwiceOops lu.haystack.init
<console>:15: error: type mismatch;
 found   : lu.Needle
 required: _1.u.Needle where val _1: NonWorkingStepTwice
       lu stepTwiceOops lu.haystack.init
                                    ^

There’s a hint in that we had to write val u, not u, nor private val u, in order for the implicit class itself to compile. This signature tells us that there’s an argument of type LittleUniverse, and a member u: LittleUniverse. However, whereas with the function examples above, we [and the compiler] could trust that they’re one and the same, we have no such guarantee here. So we don’t know that an lu.Needle is a u.Needle. We didn’t get far enough, but we don’t know that a u.Needle is an lu.Needle, either.

Relatable variables

Instead, we have to expand a little bit, and take advantage of a very interesting, if obscure, element of the type equivalence rules in the Scala language.

class WorkingStepTwice[U <: LittleUniverse](val u: U) {
  def stepTwice(n: u.Needle): u.Needle =
    u.haystack.iter(u.haystack.iter(n))
}

implicit def WorkingStepTwice[U <: LittleUniverse](u: U)
    : WorkingStepTwice[u.type] =
  new WorkingStepTwice(u)

Unfortunately, the ritual of expanding the implicit class shorthand is absolutely necessary; the implicit class won’t generate the dependent-method-typed implicit conversion we need.

Now we can get the proof we need.

scala> lu stepTwice lu.haystack.init
res7: _1.u.Needle forSome { val _1: WorkingStepTwice[lu.type] } = Needle()

// that's a little weird, but reduces to what we need
scala> res7: lu.Needle
res8: lu.Needle = Needle()

How does this work?

  1. Implicitly convert lu, giving us a conv: WorkingStepTwice[lu.type].
  2. This means that conv.u: lu.type, by expansion of U.
  3. This in turn means that conv.u.type <: lu.type.

The next part is worth taking in two parts. It may be worth having §3.5.2 “Conformance” of the language spec open for reference. First, let’s consider the return type (a covariant position), which is simpler.

  1. The return type expands to conv.u.type#Needle.
  2. The ninth conformance bullet point tells us that the left side of a # projection is covariant, so because conv.u.type <: lu.type (see above), the return type widens to lu.type#Needle.
  3. For this, lu.Needle is a shorthand.

It was far longer until I realized how the argument type works. You’ll want to scroll up on the SLS a bit, to the “Equivalence” section. Keep in mind that we are trying to widen lu.Needle to conv.u.Needle, which is the reverse of what we did for the return type.

  1. Our argument’s type expands to lu.type#Needle.
  2. The second bullet point under “Equivalence” says that “If a path p has a singleton type q.type, then p.type ≡ q.type.” From this, we can derive that conv.u.type = lu.type. This is a stronger conclusion than we reached above!
  3. We substitute the left side of the # using the equivalence, giving us conv.u.type#Needle.

I cannot characterize this feature of the type system as anything other than “really freaky” when you first encounter it. It seems like an odd corner case. Normally, when you write val x: T, then x.type is a strict subtype of T, and you can count on that, but this carves out an exception to that rule. It is sound, though, and an absolutely essential feature!

val sameLu: lu.type = lu

scala> sameLu.haystack.iter(lu.haystack.init)
res9: sameLu.Needle = Needle()

Without this rule, even though we have given it the most specific type possible, sameLu couldn’t be a true substitute for lu in all scenarios. That means that in order to make use of singleton type indices, we would be forever beholden to the variable we initially stored the value in. I think this would be extremely inconvenient, structurally, in almost all useful programs.

With the rule in place, we can fully relate the lu and conv.u variables, to let us reorganize how we talk about universes and values indexed by their singleton types in many ways.

A pointless argument

Let’s try to hide the universe. We don’t need it, after all. We can’t refer to u in the method signature anymore, so let’s try the same conversion we used with twoInitsFromAHaystack. We already have the U type parameter, after all.

class CleanerStepTwice[U <: LittleUniverse](private val u: U) {
  def stepTwiceLively(n: U#Needle): U#Needle =
    ???
}

implicit def CleanerStepTwice[U <: LittleUniverse](u: U)
    : CleanerStepTwice[u.type] =
  new CleanerStepTwice(u)

This has the proper signature, and it’s cleaner, since we don’t expose the unused-at-runtime u variable anymore. We could refine a little further, and replace it with a U#Haystack, just as with twoInitsFromAHaystack.

This gives us the same interface, with all the index preservation we need. Even better, it infers a nicer return type.

scala> def trial = lu stepTwiceLively lu.haystack.init
trial: lu.Needle

Now, let’s turn to implementation.

class OnceMoreStepTwice[U <: LittleUniverse](u: U) {
  def stepTwiceFinally(n: U#Needle): U#Needle =
    u.haystack.iter(u.haystack.iter(n))
}

<console>:18: error: type mismatch;
 found   : U#Needle
 required: OnceMoreStepTwice.this.u.Needle
           u.haystack.iter(u.haystack.iter(n))
                                           ^

This is the last part of the escape! If this worked, we could fully erase the LittleUniverse from most code, relying on the pure type-level index to prove enough of its existence! So it’s a little frustrating that it doesn’t quite work.

Let’s break it down. First, the return type is fine.

  1. Since u: U, u.type <: U. (This is true, and useful, in the scope of u, which is now invisible to the caller.)
  2. iter returns a u.type#Needle.
    • Note: since u is not in scope for the caller, if we returned this as is, it would effectively widen to the existentially bound u.type#Needle forSome {val u: U}. But the same logic in the next step would apply to that type.
  3. By the # left side covariance, u.type#Needle widens to U#Needle.

Pretty simple, by the standards of what we’ve seen so far.

Contravariance is the root of all…

But things break down when we try to call iter(n). Keep in mind that n: U#Needle and the expected type is u.Needle. Specifically: since we don’t know in the implementation that U is a singleton type, we can’t use the “singleton type equivalence” rule on it! But suppose that we could; that is, suppose that we could constrain U to be a singleton type.

  1. The argument type is U#Needle.
  2. By singleton equivalence, since u: U and u is stable, so u.type = U.
  3. By substituting the left-hand side of the #, we get u.type#Needle.
  4. This shortens to u.Needle.

If we are unable to constrain U in this way, though, we are restricted to places where U occurs in covariant position when using cake-extracted APIs. We can invoke functions like init, because they only have the singleton index occurring in covariant position.

Invoking functions like iter, where the index occurs in contravariant or invariant position, requires being able to add this constraint, so that we can use singleton equivalence directly on the type variable U. This is quite a bit trickier.

Extracting more types

We have the same problem with the function version.

def stepTwiceHaystack[U <: LittleUniverse](
    h: U#Haystack, n: U#Needle): U#Needle =
  h.iter(h.iter(n))

<console>:18: error: type mismatch;
 found   : U#Needle
 required: _1.Needle where val _1: U
         h.iter(h.iter(n))
                       ^

Let’s walk through it one more time.

  1. n: U#Needle.
  2. h.iter expects a u.type#Needle for all val u: U.
  3. Suppose that we constrain U to be a singleton type:
    1. [The existential] u.type = U, by singleton equivalence.
    2. By # left side equivalence, h.iter expects a U#Needle.

The existential variable complicates things, but the rule is sound.

As a workaround, it is commonly suggested to extract the member types in question into separate type variables. This works in some cases, but let’s see how it goes in this one.

def stepTwiceExUnim[N, U <: LittleUniverse{type Needle = N}](
    h: U#Haystack, n: N): N = ???

This looks a lot weirder, but should be able to return the right type.

scala> def trial2 = stepTwiceExUnim[lu.Needle, lu.type](lu.haystack, lu.haystack.init)
trial2: lu.Needle

But this situation is complex enough for the technique to not work.

def stepTwiceEx[N, U <: LittleUniverse{type Needle = N}](
    h: U#Haystack, n: N): N =
  h.iter(h.iter(n))

<console>:18: error: type mismatch;
 found   : N
 required: _1.Needle where val _1: U
         h.iter(h.iter(n))
                       ^

Instead, we need to index Haystack directly with the Needle type, that is, add a type parameter to Haystack so that its Needle arguments can be talked about completely independently of the LittleUniverse, and then to write h: U#Haystack[N] above. Essentially, this means that any time a type talks about another type in a Universe, you need another type parameter to redeclare a little bit of the relationships between types in the universe.

The problem with this is that we already declared those relationships by declaring the universe! All of the non-redundant information is represented in the singleton type index. So even where the above type-refinement technique works (and it does in many cases), it’s still redeclaring things that ought to be derivable from the “mere” fact that U is a singleton type.

The fact that it’s a singleton type

(The following is based on enlightening commentary by Daniel Urban on an earlier draft.)

Let’s examine the underlying error in stepTwiceEx more directly.

scala> def fetchIter[U <: LittleUniverse](
    h: U#Haystack): U#Needle => U#Needle = h.iter
<console>:14: error: type mismatch;
 found   : _1.type(in method fetchIter)#Needle
             where type _1.type(in method fetchIter) <: U with Singleton
 required: _1.type(in value $anonfun)#Needle
             where type _1.type(in value $anonfun) <: U with Singleton
           h: U#Haystack): U#Needle => U#Needle = h.iter
                                                    ^

It’s a good thing that this doesn’t compile. If it did, we could do

fetchIter[LittleUniverse](lu.haystack)(anotherU.haystack.init)

Which is unsound.

§3.2.1 “Singleton Types” of the specification mentions this Singleton, which is in a way related to singleton types.

A stable type is either a singleton type or a type which is declared to be a subtype of trait scala.Singleton.

Adding with Singleton to the upper bound on U causes fetchIter to compile! This is sound, because we are protected from the above problem with the original fetchIter.

def fetchIter[U <: LittleUniverse with Singleton](
    h: U#Haystack): U#Needle => U#Needle = h.iter

scala> fetchIter[lu.type](lu.haystack)
res3: lu.Needle => lu.Needle = $$Lambda$1397/1159581520@683e7892

scala> fetchIter[LittleUniverse](lu.haystack)
<console>:16: error: type arguments [LittleUniverse] do not conform
                     to method fetchIter's type parameter bounds
                     [U <: LittleUniverse with Singleton]
       fetchIter[LittleUniverse](lu.haystack)
                ^

Let’s walk through the logic for fetchIter. The expression h.iter has type u.Needle => u.Needle for some val u: U, and our goal type is U#Needle => U#Needle. So we have two subgoals: prove u.Needle <: U#Needle for the covariant position (after =>), and U#Needle <: u.Needle for the contravariant position (before =>).

First, covariant:

  1. Since u: U, u.type <: U.
  2. Since the left side of # is covariant, #1 implies u.type#Needle <: U#Needle.
  3. This re-sugars to u.Needle <: U#Needle, which is the goal.

Secondly, contravariant. We’re going to have to make a best guess here, because it’s not entirely clear to me what’s going on.

  1. Since [existential] path u has a singleton type U (if we define “has a singleton type” as “having a type X such that X <: Singleton”), so u.type = U by the singleton equivalence.
  2. Since equivalence implies conformance, according to the first bullet under “Conformance”, #1 implies U <: u.type.
  3. Since the left side of # is covariant, #2 implies that U#Needle <: u.type#Needle.
  4. This resugars to U#Needle <: u.Needle, which is the goal.

I don’t quite understand this, because U doesn’t seem to meet the requirements for “singleton type”, according to the definition of singleton types. However, I’m fairly sure it’s sound, since type stability seems to be the property that lets us avoid the universe-mixing unsoundness. Unfortunately, it only seems to work with existential vals; we seem to be out of luck with vals that the compiler can still see.

// works fine!
def stepTwiceSingly[U <: LittleUniverse with Singleton](
    h: U#Haystack, n: U#Needle): U#Needle = {
  h.iter(h.iter(n))
}

// but alas, this form doesn't
class StepTwiceSingly[U <: LittleUniverse with Singleton](u: U) {
  def stepTwiceSingly(n: U#Needle): U#Needle =
    u.haystack.iter(u.haystack.iter(n))
}

<console>:15: error: type mismatch;
 found   : U#Needle
 required: StepTwiceSingly.this.u.Needle
           u.haystack.iter(u.haystack.iter(n))
                                            ^

We can work around this by having the second form invoke the first with the Haystack, thus “existentializing” the universe. I imagine that most, albeit not all, cakes can successfully follow this strategy.

So, finally, we’re almost out of the cake.

  1. Escape covariant positions with universe variable: complete.
  2. Escape contravariant/invariant positions with universe variable: complete.
  3. Escape covariant positions with universe singleton type: complete!
  4. Escape contravariant/invariant positions with universe singleton type: 90% there!

This article was tested with Scala 2.12.1.

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

Equivalence versus Equality

$
0
0

This is a guest post by Tomas Mikula. It was initially published as a document in the hasheq. It has been slightly edited and is being republished here with the permission of the original author.

This article describes what we mean when we say that the data structures in this library are equivalence-aware in a type-safe fashion.

Equivalence

Set is a data structure that doesn’t contain duplicate elements. An implementation of Set must therefore have a way to compare elements for “sameness”. A useful notion of sameness is equivalence, i.e. a binary relation that is reflexive, symmetric and transitive. Any reasonable implementation of Set is equipped with some equivalence relation on its element type.

Here’s the catch: For any type with more than one inhabitant there are multiple valid equivalence relations. We cannot (in general) pick one that is suitable in all contexts. For example, are these two binary trees same?

  +            +
 / \          / \
1   +        +   3
   / \      / \
  2   3    1   2

It depends on the context. They clearly have different structure, but they are both binary search trees containing the same elements. For a balancing algorithm, they are different trees, but as an implementation of Set, they represent the same set of integers.

Equality

Despite the non-uniqueness, there is one equivalence relation that stands out: equality. Two objects are considered equal when they are indistinguishable to an observer. Formally, equality is required to have the substitution property:

\[ \forall a,b \in A, \forall f \in (A \to B): a=_A b \implies f(a)=_B f(b) \]

(Here, $=_A$ denotes equality on $A$, $=_B$ denotes equality on $B$.)

Equality is the finest equivalence: whenever two elements are equal, they are necessarily equivalent with respect to every equivalence.

Choices in libraries

Popular Scala libraries take one of these two approaches when dealing with comparing elements for “sameness”.

The current approach of cats is equality. Instances of the cats.Eq[A] typeclass are required to have all the properties of equality, including the substitution property above. The problem with this approach is that for some types, such as Set[Int], equality is too strict to be useful: Are values Set(1, 2) and Set(2, 1) equal? For that to be true, they have to be indistinguishable by any function. Let’s try (_.toList):

scala> Set(1, 2).toList == Set(2, 1).toList
res0: Boolean = false

So, Set(1, 2) and Set(2, 1) are clearly not equal. As a result, we cannot use Set[Int] in a context where equality is required (without cheating).

On the other hand, scalaz uses unspecified equivalence. Although the name scalaz.Equal[A] might suggest equality, instances of this typeclass are only tested for properties of equivalence. As mentioned above, there are multiple valid equivalence relations for virtually any type. When there are also multiple useful equivalences for a type, we are at risk of mixing them up (and the fact that they are usually resolved as implicit arguments only makes things worse).

Equivalence-aware sets (a.k.a. setoids)

Let’s look at how we deal with this issue. We define typeclass Equiv with an extra type parameter that serves as a “tag” identifying the meaning of the equivalence.

trait Equiv[A, Eq] {
  def equiv(a: A, b: A): Boolean
}
// defined trait Equiv

For the compiler, the “tag” is an opaque type. It only has specific meaning for humans. The only meaning it has for the compiler is that different tags represent (intensionally) different equivalence relations.

An equivalence-aware data structure then carries in its type the tag of the equivalence it uses.

import hasheq._
// import hasheq._

import hasheq.immutable._
// import hasheq.immutable._

import hasheq.std.int._
// import hasheq.std.int._
scala> HashSet(1, 2, 3, 4, 5)
res0: hasheq.immutable.HashSet[Int] = HashSetoid(5, 1, 2, 3, 4)

What on earth is HashSetoid? A setoid is an equivalence-aware set. HashSetoid is then just a setoid implementated using hash-table. Let’s look at the definition of HashSet:

type HashSet[A] = HashSetoid[A, Equality.type]

So HashSet is just a HashSetoid whose equivalence is equality. To create an instance of HashSet[Int] above, we needed to have an implicit instance of Equiv[Int, Equality.type] in scope.

implicitly[Equiv[Int, Equality.type]]

For the compiler, Equality is just a rather arbitrary singleton object. It only has the meaning of mathematical equality for us, humans.

There is a convenient type alias provided for equality relation:

type Equal[A] = Equiv[A, Equality.type]
implicitly[Equal[Int]]

So how do we deal with the problem of set equality mentioned above, i.e. that HashSet(1, 2) and HashSet(2, 1) are not truly equal? We just don’t provide a definition of equality for HashSet[Int].

scala> implicitly[Equal[HashSet[Int]]]
<console>:22: error: could not find implicit value for parameter e: hasheq.Equal[hasheq.immutable.HashSet[Int]]
       implicitly[Equal[HashSet[Int]]]
                 ^

But that means we cannot have a HashSet[HashSet[Int]]! (Remember, for a HashSet[A], we need an instance of Equal[A], and we just showed we don’t have an instance of Equal[HashSet[Int]].)

scala> HashSet(HashSet(1, 2, 3, 4, 5))
<console>:22: error: could not find implicit value for parameter A: hasheq.Hash[hasheq.immutable.HashSet[Int]]
       HashSet(HashSet(1, 2, 3, 4, 5))
              ^

But we can have a HashSetoid[HashSet[Int], E], where E is some equivalence on HashSet[Int].

scala> HashSet.of(HashSet(1, 2, 3, 4, 5))
res5: hasheq.immutable.HashSetoid[hasheq.immutable.HashSet[Int],hasheq.immutable.Setoid.ContentEquiv[Int,hasheq.Equality.type]] = HashSetoid(HashSetoid(5, 1, 2, 3, 4))

HashSet.of(elems) is like HashSet(elems), except it tries to infer the equivalence on the element type, instead of requiring it to be equality.

Notice the equivalence tag: Setoid.ContentEquiv[Int, Equality.type]. Its meaning is (again, for humans only) that two setoids are equivalent when they contain the same elements (here, of type Int), as compared by the given equivalence of elements (here, Equality).

The remaining question is: How does this work in the presence of multiple useful equivalences?

Let’s define another equivalence on Int (in addition to the provided equality).

// Our "tag" for equivalence modulo 10.
// This trait will never be instantiated.
sealed trait Mod10

// Provide equivalence tagged by Mod10.
implicit object EqMod10 extends Equiv[Int, Mod10] {
  def mod10(i: Int): Int = {
    val r = i % 10
    if (r < 0) r + 10
    else r
  }
  def equiv(a: Int, b: Int): Boolean = mod10(a) == mod10(b)
}

// Provide hash function compatible with equivalence modulo 10.
// Note that the HashEq typeclass is also tagged by Mod10.
implicit object HashMod10 extends HashEq[Int, Mod10] {
  def hash(a: Int): Int = EqMod10.mod10(a)
}

Now let’s create a “setoid of sets of integers”, as before.

scala> HashSet.of(HashSet(1, 2, 3, 4, 5))
res13: hasheq.immutable.HashSetoid[hasheq.immutable.HashSet[Int],hasheq.immutable.Setoid.ContentEquiv[Int,hasheq.Equality.type]] = HashSetoid(HashSetoid(5, 1, 2, 3, 4))

This still works, because HashSet requires an equality on Int, and there is only one in the implicit scope (the newly defined equivalence EqMod10 is not equality). Let’s try to create a “setoid of setoids of integers”:

scala> HashSet.of(HashSet.of(1, 2, 3, 4, 5))
<console>:24: error: ambiguous implicit values:
 both method hashInstance in object int of type => hasheq.Hash[Int]
 and object HashMod10 of type HashMod10.type
 match expected type hasheq.HashEq[Int,Eq]
       HashSet.of(HashSet.of(1, 2, 3, 4, 5))
                            ^

This fails, because there are now more equivalences on Int in scope. (There are now also multiple hash functions, which is what the error message actually says.) We need to be more specific:

scala> HashSet.of(HashSet.of[Int, Mod10](1, 2, 3, 4, 5))
res15: hasheq.immutable.HashSetoid[hasheq.immutable.HashSetoid[Int,Mod10],hasheq.immutable.Setoid.ContentEquiv[Int,Mod10]] = HashSetoid(HashSetoid(5, 1, 2, 3, 4))

Finally, does it prevent mixing up equivalences? Let’s see:

scala> val s1 = HashSet(1,  2,  3,         11, 12, 13    )
s1: hasheq.immutable.HashSet[Int] = HashSetoid(1, 13, 2, 12, 3, 11)

scala> val s2 = HashSet(    2,  3,  4,  5,         13, 14)
s2: hasheq.immutable.HashSet[Int] = HashSetoid(5, 14, 13, 2, 3, 4)

scala> val t1 = HashSet.of[Int, Mod10](1,  2,  3,         11, 12, 13    )
t1: hasheq.immutable.HashSetoid[Int,Mod10] = HashSetoid(1, 2, 3)

scala> val t2 = HashSet.of[Int, Mod10](    2,  3,  4,  5,         13, 14)
t2: hasheq.immutable.HashSetoid[Int,Mod10] = HashSetoid(5, 2, 3, 4)

Combining compatible setoids:

scala> s1 union s2
res16: hasheq.immutable.HashSetoid[Int,hasheq.Equality.type] = HashSetoid(5, 14, 1, 13, 2, 12, 3, 11, 4)

scala> t1 union t2
res17: hasheq.immutable.HashSetoid[Int,Mod10] = HashSetoid(5, 1, 2, 3, 4)

Combining incompatible setoids:

scala> s1 union t2
<console>:26: error: type mismatch;
 found   : hasheq.immutable.HashSetoid[Int,Mod10]
 required: hasheq.immutable.HashSetoid[Int,hasheq.Equality.type]
       s1 union t2
                ^

scala> t1 union s2
<console>:26: error: type mismatch;
 found   : hasheq.immutable.HashSet[Int]
    (which expands to)  hasheq.immutable.HashSetoid[Int,hasheq.Equality.type]
 required: hasheq.immutable.HashSetoid[Int,Mod10]
       t1 union s2
                ^

Conclusion

We went one step further in the direction of type-safe equivalence in Scala compared to what is typically seen out in the wild today. There is nothing very sophisticated about this encoding. I think the major win is that we can design APIs so that the extra type parameter (the “equivalence tag”) stays unnoticed by the user of the API as long as they only deal with equalities. As soon as the equivalence tag starts requesting our attention (via an ambiguous implicit or a type error), it is likely that the attention is justified.

This article was tested with Scala 2.11.8 and hasheq version 0.3.

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

An IO monad for cats

$
0
0

Haskell is a pure language. Every Haskell expression is referentially transparent, meaning that you can substitute that expression with its evaluated result without changing the program. Or, put into code:

-- this program
f expr expr -- apply function f to arguments expr, expr

-- is equivalent to this one, which factors out `expr`
let
  x = expr -- introduce a new variable `x` with the value of `expr`
in
  f x x

And this is true for all expressions e, and all functions f. These could be complex expressions which describe ways of manipulating network channels or window buffers, or something trivial like a numeric literal. You can always substitute the expression with its value.

This is not true in Scala, simply because Scala allows unrestricted side-effects. Unlike Haskell, Scala puts no limitations on where and when we can use things like mutable state (vars) or evaluated external effects like println or launchTheMissiles. Since there are no restrictions on where and when we can do evil, the Scala equivalent to the above just doesn’t work:

f(e, e)
// isn't really equivalent to!
val x = e
f(x, x)

The reason it isn’t equivalent comes from the different sorts of expressions that we could find in e. For example, what if e is println("hi!"). If we make that substitution, our snippet looks like the following:

f(println("hi"), println("hi"))
// isn't really equivalent to!
val x = println("hi")
f(x, x)

Clearly these are not the same two programs. The first prints "hi" twice, while the second only prints it once. This is a violation of referential transparency, and it’s why we sometimes say that Scala is an impure language. Any expression which is not referentially transparent must contain side-effects, by definition.

Now of course, we found this problem by using a side-effecting function: namely, println. Haskell clearly has the ability to print to standard output, so how does it avoid this issue? If we build the same program in Haskell, can we violate referential transparency?

f (putStrLn "hi") (putStrLn "hi")
-- is equivalent to
let x = putStrLn "hi" in f x x

As it turns out, this is still referentially transparent! These two programs still have the same meaning. This is possible only because neither program actually prints anything!

In Haskell, effects are treated as first-class values. The putStrLn function doesn’t print to standard out, it returns a value (of type IO ()) which describes how to print to standard out, but stops short of actually doing it. These sorts of values can be composed using the monadic operators (in Scala, flatMap and pure), allowing Haskell programmers to build up expressions composed of sequences of dependent effects, all of which are merely descriptions of the side-effects which will eventually be performed by the runtime. Ultimately, the description which comprises your whole program is the return result from the main function. The Haskell runtime runs the main function to get this description of all your effects, and then runs the effects per your instructions.

This is kind of a clever trick. It allows Haskell to simultaneously be pure and still have excellent support for manipulating effects and interacting with the “real world”. But why is it relevant to Scala? After all, Scala is an impure language. We don’t need to go through this complex rigmarole of describing our effects and composing those descriptions; the language lets us just do it! So why wouldn’t we just, you know, evaluate the effects that we need evaluated?

The answer is that we want to reason about where and when our effects are evaluated. And of course, we want to be able to leverage laws and abstractions which assume equational semantics for expressions (i.e. referential transparency). Cats is full of these sorts of abstractions, and cats-laws provides a vast set of laws which describe them. But all of these abstractions and all of these laws break down the moment you introduce some sort of side-effecting expression. Because, much like our referential transparency example from earlier, these abstractions assume that you can substitute expressions with their evaluated results, and that’s just not true in the presence of side-effects.

What we need is a data type which allows us to encapsulate Scala-style side-effects in the form of a pure value, on which referential transparency holds and which we can compose using other well-defined abstractions, such as Monad. Scalaz defines two such data types which meet these criteria: scalaz.effect.IO and scalaz.concurrent.Task. But in practice, nearly everyone uses Task instead of IO because of its support for asynchronous effects.

Cats does not define any such abstraction, and what’s worse is the cats ecosystem also doesn’t really provide any such abstraction. There are two Task implementations that are relatively commonly used with cats – namely, monix.eval.Task and fs2.Task – but these are not part of cats per se, nor are they deeply integrated into its abstraction hierarchy. Additionally, the proliferation of broadly equivalent options has led to confusion in the ecosystem, with middleware authors often forced to choose a solution for their end-users, and end-users uncertain as to which choice is “right”.

Introducing cats-effect

The cats-effect project aims to change all of that. The goal of cats-effect is to provide an “easy default” IO type for the cats ecosystem, deeply integrated with cats-core, with all of the features and performance that are required for real world production use. Additionally, cats-effect defines a set of abstractions in the form of several typeclasses which describe what it means to be a pure effect type. These abstractions are extremely useful both in enabling MTL-style program composition and to ensure that other pre-existing Task implementations remain first-class citizens of the ecosystem. IO does not overshadow monix.eval.Task or fs2.Task; it complements them by providing a set of abstractions and laws which allow users to write safe, parametric code which supports each of them equally.

One important sidebar here: cats-effect does not provide any concurrency primitives. scalaz.concurrent.Task and monix.eval.Task are both notable for providing functions such as both, which takes two Tasks and runs them in parallel, returning a Task of a tuple of the results. The cats.effect.IO type does not provide any such function, and while it would be possible to define such a function (and others like it!), we strongly encourage users to instead consider full-on streaming frameworks such as fs2 or Monix for their concurrency needs, as these frameworks are able to provide a much sounder foundation for such functions. See here for a rough outline of why this is. Also note that some Task implementations, such as Monix’s, can and do provide parallelism on a sound foundation by enriching their internal algebraic structures. Thus, monix.eval.Task is actually quite different from cats.effect.IO, despite having a similar core set of operations.

Enough Talk…

What does this look like in practice? Well, ideally, as convenient as possible! Let’s look at our println example:

def putStrLn(line: String): IO[Unit] =
  IO { println(line) }

f(putStrLn("hi!"), putStrLn("hi!"))

// is equivalent to

val x = putStrLn("hi!")
f(x, x)

Great! We can write Haskell fanfic in Scala. 😛

The notable element here is the use of the IO.apply constructor to wrap the println effect in a pure IO value. This pattern can be applied to any side-effect. You can think of this sort of like an FFI that converts impure code (like println) into pure code (like putStrLn). The goal of this API was to be as simple and straightforward as possible. If you have a curly brace block of impure side-effecting code, you can wrap it in a composable and pure abstraction by just adding two characters: IO. You can wrap arbitrarily large or small blocks of code, potentially involving complex allocations, JNI calls, resource semantics, etc; but it is generally considered a best practice to wrap side-effects into the smallest composable units that make sense and do all of your sequentialization using flatMap and for-comprehensions.

For example, here’s a program that performs some simple user interaction in the shell:

import cats.effect.IO

val program = for {
  _ <- IO { println("Welcome to Scala!  What's your name?") }
  name <- IO { Console.readLine }
  _ <- IO { println(s"Well hello, $name!") }
} yield ()

We could have just as easily written this program in the following way:

val program = IO {
  println("Welcome to Scala!  What's your name?")
  val name = Console.readLine
  println(s"Well hello, $name!")
}

But this gives us less flexibility for composition. Remember that even though program is a pure and referentially transparent value, its definition is not, which is to say that IO { expr } is not the same as val x = expr; IO { x }. Anything inside the IO {} block is not referentially transparent, and so should be treated with extreme care and suspicion. The less of our program we have inside these blocks, the better!

As a sidebar that is actually kinda cool, we can implement a readString IO action that wraps Console.readLine as a val!

val readString = IO { Console.readLine }

This is totally valid! We don’t need to worry about the difference between def and val anymore, because IO is referentially transparent. So you use def when you need parameters, and you use val when you don’t, and you don’t have to think about evaluation semantics. No more subtle bugs caused by accidentally memoizing your effects!

Of course, if program is referentially transparent, then clearly repeated values of program cannot possibly run the effects it represents multiple times. For example:

program
program
program

// must be the same as!

program

If this weren’t the case, then we would be in trouble when trying to construct examples like the Haskell one from earlier. But there is an implication here that is quite profound: IO cannot eagerly evaluate its effects, and similarly cannot memoize its results! If IO were to eagerly evaluate or to memoize, then we could no longer replace references to the expression with the expression itself, since that would result in a different IO instance to be evaluated separately.

This is precisely why scala.concurrent.Future is not a suitable type for encapsulating effects in this way: constructing a Future that will eventually side-effect is itself a side-effect! Future evaluates eagerly (sort of, see below) and memoizes its results, meaning that a println inside of a given Future will only evaluate once, even if the Future is sequenced multiple times. This in turn means that val x = Future(...); f(x, x) is not the same program as f(Future(...), Future(...)), which is the very definition of a violation of referential transparency.

Coming back to IO… If program does not evaluate eagerly, then clearly there must be some mechanism for asking it to evaluate. After all, Scala is not like Haskell: we don’t return a value of type IO[Unit] from our main function. IO provides an FFI of sorts for wrapping side-effecting code into pure IO values, so it must also provide an FFI for going in the opposite direction: taking a pure IO value and evaluating its constituent actions as side-effects.

program.unsafeRunSync()    // uh oh!

This function is called unsafeRunSync(). Given an IO[A], the unsafeRunSync() function will give you a value of type A. You should only call this function once, ideally at the very end of your program! (i.e. in your main function) Just as with IO.apply, any expression involving unsafeRunSync() is not referentially transparent. For example:

program.unsafeRunSync()
program.unsafeRunSync()

The above will run program twice. So clearly, referential transparency is out the window whenever we do this, and we cannot expect the normal laws and abstractions to remain sound in the presence of this function.

A sidebar on Future’s eager evaluation

As Viktor Klang is fond of pointing out, Future doesn’t need to evaluate eagerly. It is possible to define an ExecutionContext in which Future defers its evaluation until some indefinitely later point. However, this is not the default mode of operation for 99% of all Futures ever constructed; most people just use ExecutionContext.global and leave it at that. Additionally, if someone hands me an arbitrary Future, perhaps as a return value from a function, I really have no idea whether or not that Future is secretly running without my consent. In other words, the referential transparency (or lack thereof) of functions that I write using Future is dependent on the runtime configuration of some other function which is hidden from me. That’s not referential transparency anymore. Because we cannot be certain that Future is deferring its evaluation, we must defensively assume that it is not.

This, in a nutshell, is precisely why Future is not appropriate for functional programming. IO provides a pair of functions (fromFuture and unsafeToFuture) for interacting with Future-using APIs, but in general, you should try to stick with IO as much as possible when manipulating effects.

Asynchrony and the JVM

Scala runs on three platforms: the JVM, JavaScript and LLVM. For the moment, we’ll just focus on the first two. The JVM has support for multiple threads, but those threads are native (i.e. kernel) threads, meaning that they are relatively expensive to create and maintain in the runtime. They are a very limited resource, sort of like file handles or heap space, and you can’t just write programs which require an unbounded number of them. The exact upper bound on the JVM varies from platform to platform, and varies considerably depending on your GC configuration, but a general rule of thumb is “a few thousand”, where “few” is a small number. In practice, you’re going to want far less threads than that if you want to avoid thrashing your GC, and most applications will divide themselves into a bounded “main” thread pool (usually bounded to exactly the number of CPUs) on which all CPU-bound tasks are performed and most of the program runs, as well as a set of unbounded “blocking” thread pools on which blocking IO actions (such as anything in java.io) are run. When you add NIO worker pools into the mix, the final number of threads in a practical production service is usually around 30-40 on an 8 CPU machine, growing roughly linearly as you add CPUs. Clearly, this is not a very large number.

On JavaScript runtimes (such as node or in the browser), the situation is even worse: you have exactly one thread! JavaScript simply doesn’t have multi-threading in any (real) form, and so it’s like the JVM situation, but 30-40x more constraining.

For this reason, we need to be very careful when writing Scala to treat threads as an extremely scarce resource. Blocking threads (using mechanisms such as wait, join or CountDownLatch) should be considered absolutely anathema, since it selfishly wastes a very finite and very critical resource, leading to thread starvation and deadlocks.

This is very different from how things are in Haskell though! The Haskell runtime is implemented around the concept of green threads, which is to say, emulated concurrency by means of a runtime dispatch lock. Haskell basically creates a global bounded thread pool in the runtime with the same number of threads as your machine has CPUs. On top of that pool, it runs dispatch trampolines that schedule and evict expression evaluation, effectively emulating an arbitrarily large number of “fake” threads atop a small fixed set of “real” threads. So when you write code in Haskell, you generally just assume that threads are extremely cheap and you can have as many of them as you want. Under these circumstances, blocking a thread is not really a big deal (as long as you don’t do it in FFI native code), so there’s no reason to go out of your way to avoid it in abstractions like IO.

This presents a bit of a dilemma for cats-effect: we want to provide a practical pure abstraction for encapsulating effects, but we need to run on the JVM and on JavaScript which means we need to provide a way to avoid thread blocking. So, the IO implementation in cats-effect is going to necessarily end up looking very, very different from the one in Haskell, providing a very different set of operations.

Specifically, cats.effect.IO provides an additional constructor, async, which allows the construction of IO instances from callback-driven APIs. This is generally referred to as “asynchronous” control flow, as opposed to “synchronous” control flow (represented by the apply constructor). To see how this works, we’re going to need a bit of setup.

Consider the following somewhat-realistic NIO API (translated to Scala):

trait Response[T] {
  def onError(t: Throwable): Unit
  def onSuccess(t: T): Unit
}
// defined trait Response

trait Channel {
  def sendBytes(chunk: Array[Byte], handler: Response[Unit]): Unit
  def receiveBytes(handler: Response[Array[Byte]]): Unit
}
// defined trait Channel

This is an asynchronous API. Neither of the functions sendBytes or receiveBytes attempt to block on completion. Instead, they schedule their operations via some underlying mechanism. This interface could be implemented on top of java.io (which is a synchronous API) through the use of an internal thread pool, but most NIO implementations are actually going to delegate their scheduling all the way down to the kernel layer, avoiding the consumption of a precious thread while waiting for the underlying IO – which, in the case of network sockets, may be a very long wait indeed!

Wrapping this sort of API in a referentially transparent and uniform fashion is a very important feature of IO, precisely because of Scala’s underlying platform constraints. Clearly, sendBytes and receiveBytes both represent side-effects, but they’re different than println and readLine in that they don’t produce their results in a sequentially returned value. Instead, they take a callback, Response, which will eventually be notified (likely on some other thread!) when the result is available. The IO.async constructor is designed for precisely these situations:

def send(c: Channel, chunk: Array[Byte]): IO[Unit] = {
  IO async { cb =>
    c.sendBytes(chunk, new Response[Unit] {
      def onError(t: Throwable) = cb(Left(t))
      def onSuccess(v: Unit) = cb(Right(()))
    })
  }
}
// send: (c: Channel, chunk: Array[Byte])cats.effect.IO[Unit]

def receive(c: Channel): IO[Array[Byte]] = {
  IO async { cb =>
    c.receiveBytes(new Response[Array[Byte]] {
      def onError(t: Throwable) = cb(Left(t))
      def onSuccess(chunk: Array[Byte]) = cb(Right(chunk))
    })
  }
}
// receive: (c: Channel)cats.effect.IO[Array[Byte]]

Obviously, this is a little more daunting than the println examples from earlier, but that’s mostly the fault of the anonymous inner class syntactic ceremony. The IO interaction is actually quite simple!

The async constructor takes a function which is handed a callback (represented above by cb in both cases). This callback is itself a function of type Either[Throwable, A] => Unit, where A is the type produced by the IO. So when our Response comes back as onSuccess in the send example, we invoke the callback with a Right(()) since we’re trying to produce an IO[Unit]. When the Response comes back as onSuccess in the receive example, we invoke the callback with Right(chunk), since the IO produces an Array[Byte].

Now remember, IO is still a monad, and IO values constructed with async are perfectly capable of all of the things that “normal”, synchronous IO values are, which means that you can use these values inside for-comprehensions and other conventional composition! This is incredibly, unbelievably nice in practice, because it takes your complex, nested, callback-driven code and flattens it into simple, easy-to-read sequential composition. For example:

val c: Channel = null // pretend this is an actual channel

for {
  _ <- send(c, "SYN".getBytes)
  response <- receive(c)

  _ <- if (response == "ACK".getBytes)   // pretend == works on Array[Byte]
    IO { println("found the guy!") }
  else
    IO { println("no idea what happened, but it wasn't good") }
} yield ()

This is kind of amazing. There’s no thread blocking at all in the above (other than the println blocking on standard output). The receive could take quite a long time to come back to us, and our thread is free to do other things in the interim. Everything is driven by callbacks under the surface, and asynchronous actions can be manipulated just as easily as synchronous ones.

Of course, this is an even bigger win on JavaScript, where nearly everything is callback-based, and gigantic, deeply nested chunks of code are not unusual. IO allows you to flatten those deeply nested chunks of code into a nice, clean, linear and sequential formulation.

Thread Shifting

Now there is a caveat here. When our Response handler is invoked by Channel, it is very likely that the callback will be run on a thread which is part of a different thread pool than our main program. Remember from earlier where I described how most well-designed Java services are organized:

  • A bounded thread pool set to num CPUs in size for any non-IO actions
  • A set of unbounded thread pools for blocking IO
  • Some bounded internal thread worker pools for NIO polling

We definitely want to run nearly everything on that first pool (which is probably ExecutionContext.global), but we’re probably going to receive the Response callback on one of the third pools. So how can we force the rest of our program (including those printlns) back onto the main pool?

The answer is the shift function.

import scala.concurrent._
implicit val ec = ExecutionContext.global

for {
  _ <- send(c, "SYN".getBytes)
  response <- receive(c).shift    // there's no place like home!

  _ <- if (response == "ACK".getBytes)   // pretend == works on Array[Byte]
    IO { println("found the guy!") }
  else
    IO { println("no idea what happened, but it wasn't good") }
} yield ()

shift’s functionality is a little complicated, but generally speaking, you should think of it as a “force this IO onto this other thread pool” function. Of course, when receive executes, most of its work isn’t done on any thread at all (since it is simply registering a hook with the kernel), and so that work isn’t thread shifted to any pool, main or otherwise. But when receive gets back to us with the network response, the callback will be handled and then immediately thread-shifted back onto the main pool, which is passed implicitly as a parameter to shift (you can also pass this explicitly if you like). This thread-shifting means that all of the subsequent actions within the for-comprehension – which is to say, the continuation of receive(c) – will be run on the ec thread pool, rather than whatever worker pool is used internally by Channel. This is an extremely common use-case in practice, and IO attempts to make it as straightforward as possible.

Another possible application of thread shifting is ensuring that a blocking IO action is relocated from the main, CPU-bound thread pool onto one of the pools designated for blocking IO. An example of this would be any interaction with java.io:

import java.io.{BufferedReader, FileReader}
// import java.io.{BufferedReader, FileReader}

def readLines(name: String): IO[Vector[String]] = IO {
  val reader = new BufferedReader(new FileReader(name))
  var back: Vector[String] = Vector.empty

  try {
    var line: String = null
    do {
      line = reader.readLine()
      back :+ line
    } while (line != null)
  } finally {
    reader.close()
  }

  back
}
// readLines: (name: String)cats.effect.IO[Vector[String]]
for {
  _ <- IO { println("Name, pls.") }
  name <- IO { Console.readLine }
  lines <- readLines("names.txt")

  _ <- if (lines.contains(name))
    IO { println("You're on the list, boss.") }
  else
    IO { println("Get outa here!") }
} yield ()

Clearly, readLines is blocking the underlying thread while it waits for the disk to return the file contents to us, and for a large file, we might be blocking the thread for quite a long time! Now if we’re treating our thread pools with respect (as described above), then we probably have a pair of ExecutionContext(s) sitting around in our code somewhere:

import java.util.concurrent.Executors

implicit val Main = ExecutionContext.global
val BlockingFileIO = ExecutionContext.fromExecutor(Executors.newCachedThreadPool())

We want to ensure that readLines runs on the BlockingFileIO pool, while everything else in the for-comprehension runs on Main. How can we achieve this?

With shift!

for {
  _ <- IO { println("Name, pls.") }
  name <- IO { Console.readLine }
  lines <- readLines("names.txt").shift(BlockingFileIO).shift(Main)

  _ <- if (lines.contains(name))
    IO { println("You're on the list, boss.") }
  else
    IO { println("Get outa here!") }
} yield ()

Now we’re definitely in bizarro land. Two calls to shift, one after the other? Let’s break this apart:

readLines("names.txt").shift(BlockingFileIO)

One of the functions of shift is to take the IO action it is given and relocate that action onto the given thread pool. In the case of receive, this component of shift was meaningless since receive didn’t use a thread under the surface (it was asynchronous!). However, readLines does use a thread under the surface (hint: it was constructed with IO.apply rather than IO.async), and so that work will be relocated onto the BlockingFileIO pool by the above expression.

Additionally, the continuation of this work will also be relocated onto the BlockingFileIO pool, and that’s definitely not what we want. The evaluation of the contains function is definitely CPU-bound, and should be run on the Main pool. So we need to shift a second time, but only the continuation of the readLines action, not readLines itself. As it turns out, we can achieve this just by adding the second shift call:

readLines("names.txt").shift(BlockingFileIO).shift(Main)

Now, readLines will be run on the BlockingFileIO pool, but the continuation of readLines (namely, everything that follows it in the for-comprehension) will be run on Main. This works because shift creates an asynchronous IO that schedules the target action on the given thread pool and invokes its continuation from a callback. The ExecutionContext#execute function should give you an idea of how this works. This means that the result of the first shift is an IO constructed with async, and cannot itself be thread-shifted (unlike an IO constructed with apply), but its continuation can be thread-shifted, which is exactly what happens.

This sort of double-shift idiom is very common in production service code that makes use of legacy blocking IO libraries such as java.io.

Synchronous vs Asynchronous Execution

Speaking of asynchrony, readers who have been looking ahead in the class syllabus probably realized that the type signature of unsafeRunSync() is more than a little suspicious. Specifically, it promises to give us an A immediately given an IO[A]; but if that IO[A] is an asynchronous action invoked with a callback, how can it achieve this promise?

The answer is that it blocks a thread. (gasp!!!) Under the surface, a CountDownLatch is used to block the calling thread whenever an IO is encountered that was constructed with IO.async. Functionally, this is very similar to the Await.result function in scala.concurrent, and it is just as dangerous. Additionally, it clearly cannot possibly work on JavaScript, since you only have one thread to block! If you try to call unsafeRunSync() on JavaScript with an underlying IO.async, it will just throw an exception rather than deadlock your application.

This is not such a great state of affairs. I mean, it works if unsafeRunSync() is being run in test code, or as the last line of your main function, but sometimes we need to interact with legacy code or with Java APIs that weren’t designed for purity. Sometimes, we just have to evaluate our IO actions before “the end of the world”, and when we do that, we don’t want to block any of our precious threads.

So IO provides an additional function: unsafeRunAsync. This function takes a callback (of type Either[Throwable, A] => Unit) which it will run when (and if) the IO[A] completes its execution. As the name implies, this function is also not referentially transparent, but unlike unsafeRunSync(), it will not block a thread.

As a sidebar that will be important in a few paragraphs, IO also defines a safe function called runAsync which has a very similar signature to unsafeRunAsync, except it returns an IO[Unit]. The IO[Unit] which is returned from this function will not block if you call unsafeRunAsync(). In other words, it is always safe to call unsafeRunSync() on the results of runAsync, even on JavaScript.

Another way to look at this is in terms of unsafeRunAsync. You can define unsafeRunAsync in terms of runAsync and unsafeRunSync():

def unsafeRunAsync[A](ioa: IO[A])(cb: Either[Throwable, A] => Unit): Unit =
  ioa.runAsync(e => IO { cb(e) }).unsafeRunSync()
// unsafeRunAsync: [A](ioa: cats.effect.IO[A])(cb: Either[Throwable,A] => Unit)Unit

This isn’t the actual definition, but it would be a valid one, and it would run correctly on every platform.

Abstraction and Lawfulness

As mentioned earlier (about 10000 words ago…), the cats-effect project not only provides a concrete IO type with a lot of nice features, it also provides a set of abstractions characterized by typeclasses and associated laws. These abstractions collectively define what it means to be a type which encapsulates side-effects in a pure fashion, and they are implemented by IO as well as several other types (including fs2.Task and monix.eval.Task). The hierarchy looks like this:

cats-effect typeclasses

Monad and MonadError are of course a part of cats-core, while everything else is in cats-effect. MonadError is functionally equivalent to the familiar scalaz.Catchable typeclass, which was commonly used in conjunction with scalaz.concurrent.Task. It literally means “a monad with error-handling capabilities”. IO certainly fits that description, as any exceptions thrown within its apply method (or within async) will be caught and may be handled in pure code by means of the attempt function. Sync, Async, LiftIO and Effect are the new typeclasses.

Sync simply describes the IO.apply function (in the typeclasses, this function is called delay). Which is to say, any type constructor F[_] which has a Sync[F] has the capability to suspend synchronous side-effecting code. Async is very similar to this in that it describes the async function. So any type constructor F[_] which has an Async[F] can suspend asynchronous side-effecting code. LiftIO should be familiar to Haskell veterans, and is broadly useful for defining parametric signatures and composing monad transformer stacks.

Effect is where everything is brought together. In addition to being able to suspend synchronous and asynchronous side-effecting code, anything that has an Effect instance may also be asynchronously interpreted into an IO. The way this is specified is using the runAsync function:

import cats.effect.{Async, LiftIO, Sync}

trait Effect[F[_]] extends Sync[F] with Async[F] with LiftIO[F] {
  def runAsync[A](fa: F[A])(cb: Either[Throwable, A] => IO[Unit]): IO[Unit]
}

What this is saying is that any Effect must define the ability to evaluate as a side-effect, but of course, we don’t want to have side-effects in our pure and reasonable code. So how are side-effects purely represented? With IO!

From a parametric reasoning standpoint, IO means “here be effects”, and so any type signature which involves IO thus also involves side-effects (well, effects anyway), and any type signature which requires side-effects must also involve IO. This bit of trickery allows us to reason about Effect in a way that would have been much harder if we had defined unsafeRunAsync as a member, and it ensures that downstream projects which write code abstracting over Effect types can do so without using any unsafe functions if they so choose (especially when taken together with the liftIO function).

Conclusion

The lack of a production-ready Task-like type fully integrated into the cats ecosystem has been a sticking point for a lot of people considering adopting cats. With the introduction of cats-effect, this should no longer be a problem! As of right now, the only releases are snapshots with hash-based versions, the latest of which can be found in the maven badge at the top of the readme. These snapshots are stable versions (in the repeatable-build sense), but they should not be considered stable, production-ready, future-proof software. We are quickly moving towards a final 0.1 release, which will depend on cats-core and will represent the stable, finalized API.

Once cats releases a final 1.0 version, cats-effect will also release version 1.0 which will depend on the corresponding version of cats-core. Changes to cats-effect are expected to be extremely rare, and thus the dependency should be considered quite stable for the purposes of upstream compatibility. Nevertheless, the release and versioning cycle is decoupled from cats-core to account for the possibility that breaking changes may need to be made independent of the cats-core release cycle.

Check out the sources! Check out the documentation. Play around with the snapshots, and let us know what you think! Now is the time to make your opinion heard. If IO in its current form doesn’t meet your needs, we want to hear about it!

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

Compile time dimensional analysis with Libra

$
0
0

Dimensional analysis

When we code, we code in numbers - doubles, floats and ints. Those numbers always represent real world quantities.

For example, the number of people in a room can be represented as an integer, as can the number of chairs. Adding people and chairs together gives a nonsensical result, but dividing the number of people by the number of chairs gives a useful indicator of how full up the room is.

val numberOfPeople = 9
val numberOfChairs = 10
numberOfPeople + numberOfChairs // this is a bug
numberOfPeople.toDouble / numberOfChairs.toDouble // this is useful

This is actually a form of dimensional analysis. We’re mentally assigning the dimension Person to the quantity of people, and Chair to the quantity of chairs. Dimensional analysis can be summarized in two laws.

  1. Quantities can only be added or subtracted to quantities of the same dimension
  2. Quantities of different dimensions can be multiplied or divided

Why is it important?

Ignoring the laws can result in serious problems. Take the Mars Climate Orbiter, a $200 million space probe which successfully reached Mars after a year long voyage, but suddenly crashed into the Martian atmosphere on arrival. Most components on the orbiter were using metric units, however a single component was sending instructions in Imperial units. The other components did not detect this, and instead began a sudden descent causing the orbiter to burn up. This was a simple unit conversion error! It was a basic mistake that could have been easily avoided. It should have been picked up during testing, or in the runtime validation layer.

In fact, it could even have been caught at compile time.

Compile time dimensional analysis

We’re going to use a similar problem to demonstrate compile time dimensional analysis. To fit with the theme of rocket physics, we will tackle a rocket launch towards the distant constellation of Libra. We’ll begin by working through our calculation in doubles before adding compile time safety with dependent types and finally supporting compile time dimensional analysis with typeclass induction.

Destination: Alpha Librae

The star that we’re aiming for is Alpha Librae. This is pretty far, so we can only send one very small person. We have been given the following quantities to work with:

  • rocket mass of a small person - 40kg
  • fuel mass of a lot of fuel - 104kg
  • exhaust speed of a decent fuel - 106ms-1
  • distance to Alpha Librae - 77 ly

We want to calculate when the rocket will arrive.

To do so, we’re going to make use of a formula known as the Ideal Rocket Equation. This calculates the speed of a rocket in ideal conditions.

val rocketSpeed = exhaustSpeed * log((rocketMass + fuelMass) / rocketMass)

Once we have the speed, we can work out the travel time.

val time = distance / rocketSpeed

Plugging the numbers in

Let’s do what we’re used to doing and use doubles:

val rocketSpeed = 1000000.0 * log((40.0 + 10000.0) / 40.0)
// rocketSpeed: Double = 5525452.939131783

val time = 77.0 / rocketSpeed
// time: Double = 1.39355091515989E-5

Fantastic! We can get to Libra in less than a day!

Unfortunately, this time estimate is too far off to be valid. We can’t get to Libra that quickly at light speed, let alone rocket speed. We’ve clearly made a mistake somewhere. Instead of pouring over our code to find out where that is, let’s try and use the compiler.

Using types

We can add some type safety to this problem by using a case class to represent each quantity.

case class Quantity[A](value: Double)

A represents the quantity dimension. So given the following dimensions:

type Kilogram
type Metre
type Second
type MetresPerSecond
type C
type LightYear
type Year

We can create quantities:

val rocketMass = Quantity[Kilogram](40.0)
val fuelMass = Quantity[Kilogram](10000.0)
val exhaust = Quantity[MetresPerSecond](1000000.0)
val distance = Quantity[LightYear](77.0)

It’s important to note that these are types, not classes. We never instantiate a MetresPerSecond - we’re just using it to differentiate between Quantity[MetresPerSecond] and Quantity[Year] at the type level.

So how does this change the code?

val rocketSpeed = Quantity[MetresPerSecond](exhaust.value * log((rocketMass.value + fuelMass.value) / rocketMass.value))
// rocketSpeed: Quantity[Types.MetresPerSecond] = Quantity(5525452.939131783)

val time = Quantity[Year](distance.value / rocketSpeed.value)
// time: Quantity[Types.Year] = Quantity(1.39355091515989E-5)

In short, it doesn’t. The code might be clearer, but we don’t know what the bug is. This is because the compiler isn’t doing anything with the types we’ve added.

Operating on quantities

We can encode our first law of addition at compile time by creating a function to add quantities:

def add[A](q0: Quantity[A], q1: Quantity[A]): Quantity[A] = Quantity(q0.value + q1.value)

This ensures that quantities can only be added to other quantities of the same type. Trying to add quantities of different types will result in a compilation error.

A quantity can also be multiplied by a dimensionless scalar value to give a quantity of the same dimension.

def times[A](q: Quantity[A], v: Double): Quantity[A] = Quantity(q.value * v)

It would be great if we could divide quantities too. Writing a divide function is more difficult:

def divide[A, B, Magic](q0: Quantity[A], q1: Quantity[B]): Quantity[Magic] =
  Quantity[Magic](q0.value / q1.value)

There’s a clear problem with trying to do this. When we divide a quantity by another, we don’t know what the Magic output type should be. The output type is dependent on what the input types are (for example, dividing Metre by Second should give MetresPerSecond). The compiler needs a way of working out what the output is, provided that it knows the input types.

Dependent types

What we actually want is a dependent type. A division operation should occur at the type level, taking two input types and supplying a dependent output type. We can create the trait Divide with a dependent output type:

trait Divide[A, B] {
  type Out
}

We also need to define an Aux type alias. This is known as the Aux pattern and makes it easier to refer to all three types at once.

object Divide {
  type Aux[A, B, Out0] = Divide[A, B] { type Out = Out0 }
}

We can create instances of this divide typeclass with different output types, so the output type is dependent on the value of the divide typeclass instance.

When dividing, the compiler looks for this implicit typeclass instance and returns a quantity corresponding to the output type.

def divide[A, B](q0: Quantity[A], q1: Quantity[B])(implicit d: Divide[A, B]): Quantity[d.Out] =
  Quantity[d.Out](q0.value / q1.value)

So given that we want to divide A by B, the compiler will look for a value of Divide[A, B] and find the Out type of it. If no instance exists, the code doesn’t compile.

We’ll need some more types to represent the result of a division:

type LightYearSecondsPerMetre
type MetresPerSecondPerC
type Dimensionless

And we’ll need to write instances for all combinations of dimensions.

implicit val kgDivideKg: Divide.Aux[Kilogram, Kilogram, Dimensionless] =
  new Divide[Kilogram, Kilogram] { type Out = Dimensionless }

implicit val lyDivideC: Divide.Aux[LightYear, C, Year] =
  new Divide[LightYear, C] { type Out = Year }

implicit val lyDivideMps: Divide.Aux[LightYear, MetresPerSecond, LightYearSecondsPerMetre] =
  new Divide[LightYear, MetresPerSecond] { type Out = LightYearSecondsPerMetre }

implicit val mpsDivideC: Divide.Aux[MetresPerSecond, C, MetresPerSecondPerC] =
  new Divide[MetresPerSecond, C] { type Out = MetresPerSecondPerC }

implicit val mpsDivideMpsPerC: Divide.Aux[MetresPerSecond, MetresPerSecondPerC, C] =
  new Divide[MetresPerSecond, MetresPerSecondPerC] { type Out = C }

And so on.

Unfortunately, there are an infinite number of combinations, so there are an infinite number of instances. Nevertheless, let’s plough on with the ones we’ve written. We can modify our rocket equation to use add, times and divide:

val rocketSpeed = times(exhaust, log(divide(add(rocketMass, fuelMass), rocketMass).value))
// rocketSpeed: Quantity[Types.MetresPerSecond] = Quantity(5525452.939131783)

val time: Quantity[Year] = divide(distance, rocketSpeed)
// <console>:31: error: type mismatch;
//  found   : Quantity[lyDivideMps.Out]
//     (which expands to)  Quantity[MoreTypes.LightYearSecondsPerMetre]
//  required: Quantity[Types.Year]
//        val time: Quantity[Year] = divide(distance, rocketSpeed)
//                                         ^

Great! We’ve caught our bug! The result was in LightYearSecondsPerMetre, not Year. We made a unit conversion error, just like the Mars orbiter.

We can now fix this by adding a conversion:

val metresPerSecondPerC: Quantity[MetresPerSecondPerC] = divide(Quantity[MetresPerSecond](300000000.0), Quantity[C](1.0))
// metresPerSecondPerC: Quantity[MoreTypes.MetresPerSecondPerC] = Quantity(3.0E8)

val speedInC = divide(rocketSpeed, metresPerSecondPerC)
// speedInC: Quantity[mpsDivideMpsPerC.Out] = Quantity(0.018418176463772612)

val time: Quantity[Year] = divide(distance, speedInC)
// time: Quantity[Types.Year] = Quantity(4180.65274547967)

It seems like it’s going to take a lot longer than we hoped to get to Libra. Perhaps it’s unwise to send a person.

Automatic derivation

We found the bug, but we needed to explicitly write out typeclass instances for every combination of dimensions. This might have worked for our small problem, but it just doesn’t scale in the long run. We need to figure out a way of deriving the typeclass instances automatically. To attempt this, we first need to generalize what a combination of dimensions actually is.

Representing dimensions

We can represent a combination of dimensions as a heterogeneous list (HList) of base dimensions. HLists are defined in shapeless, a cornerstone of most functional libraries, and can be thought of as a type level list.

type LightYearSeconds = LightYear :: Second :: HNil

This is good for multiples of dimensions, such as LightYearSeconds, but doesn’t represent combinations created from division, such as MetresPerSecond. To do this, we need some way of representing integer exponents as types. We can represent integers as types using Singleton types. We actually need these singleton types in type position. This is supported by a new feature present in Typelevel Scala, called literal types:

scalaOrganization := "org.typelevel"
scalacOptions += "-Yliteral-types"

We need to represent a key value pair of dimension and integer exponent. We could use a Tuple for this, but will use a shapeless FieldType instead. This is similar to a Tuple, but is more compatible with some of shapeless’s typeclasses.

type MetresPerSecond = FieldType[Metre, 1] :: FieldType[Second, -1] :: HNil

It’s important to note that the number 1 above is a type, not a value. Because it’s a type, the compiler can work with it.

Operations on Singleton types

When we multiply and divide dimensions, we want to add or subtract from these exponents.

We can use a library called singleton ops to do this. This provides us with type level integer operations using the OpInt typeclass:

OpInt.Aux[1 + 2, 3]
OpInt.Aux[3 * 2, 6]

It also provides a convenient alias for integer singleton types

type XInt = Singleton with Int

The type 1, for example, is a subtype of XInt.

Deriving typeclass instances

We now need to automatically derive typeclass instances of Divide. To do this, we’re going to derive instances for Invert and Multiply operations first. Deriving Divide then becomes much simpler.

The technique we’re going to use to automatically derive instances is known as typeclass induction.

Typeclass Induction

Aaron Levin gave a great introduction to induction in his talk earlier at the Typelevel Summit. In summary, you can derive an implicit typeclass instance for all cases by:

  1. Providing it for the base case
  2. Providing it for the n + 1 case, given that the n case is provided

This is similar to the mathematical method of proof by induction.

Invert

We’re first going to derive inductive typeclass instances for the Invert operation. Inverting a quantity raises it to the exponent of -1. This means that the exponents of all dimensions must be negated.

For example, the inverse of FieldType[Metre, 1] :: HNil is FieldType[Metre, -1] :: HNil. Invert takes one input type and returns one output type:

trait Invert[A] {
	type Out
}
object Invert {
	type Aux[A, Out0] = Invert[A] { type Out = Out0 }
}

To inductively derive typeclass instances for inverting, we need to prove that:

  1. We can derive an instance for HNil (the base case)
  2. We can derive an instance for a non-empty HList (the n + 1 case), provided there is an existing instance for its tail (the n case)

The base case operates on HNil

implicit def baseCase: Invert.Aux[HNil, HNil] = new Invert[HNil] { type Out = HNil }

The inductive case assumes that the tail has an instance, and derives an instance for the head by negating the exponent:

implicit def inductiveCase[D, Exp <: XInt, NExp <: XInt, Tail <: HList,
  OutTail <: HList](
    implicit negateEv: OpInt.Aux[Negate[Exp], NExp],
    tailEv: Invert.Aux[Tail, OutTail]
): Invert.Aux[FieldType[D, Exp] :: Tail, FieldType[D, NExp] :: OutTail] =
  new Invert[FieldType[D, Exp] :: Tail] {
    type Out = FieldType[D, NExp] :: OutTail
}

When the compiler looks for the implicit instance for FieldType[Metre, 1] :: HNil:

  • It finds that the inductiveCase method has a return type which fits the signature
  • It can find the required evidence negateEv for negating 1 from singleton ops
  • It requires evidence of an implicit instance for the tail HNil
  • It finds that baseCase provides this evidence

So in hunting for implicit typeclass instance for the whole list, the compiler goes and finds instances for the tail (the n case), right up until the base. If we provide an inductive proof with a baseCase and an inductiveCase, we fit the bill for what the compiler needs.

Multiply

Now that we’ve tested a basic example of induction, we can go on to a more complex one.

We want to multiply two HLists of dimensions together. This means that the exponents should be added.

trait Multiply[A, B] {
  type Out
}
object Multiply {
  type Aux[A, B, Out0] = Multiply[A, B] { type Out = Out0 }
}

This is harder to make inductive because there are two input lists involved. Luckily, we only need to recurse over one of them, as we can pick dimensions from the other using shapeless’s Selector. We will recurse over the left list and can pick elements from the right list.

Our base case can be the same:

implicit def baseCase: Multiply.Aux[HNil, HNil, HNil] = new Multiply[HNil, HNil] {
  type Out = HNil
}

We can define the inductive case using the following logic:

  1. Pick the exponent in the right list corresponding to the head dimension in the left list
  2. Add the left and right exponents together
  3. Filter the term from the right list to get the remaining elements
  4. Look for a typeclass instance for the left list tail and the remaining elements in the right list
implicit def inductiveCase[D, R <: HList, LExp <: XInt , RExp <: XInt,
  OutExp <: XInt, RTail <: HList, LTail <: HList, OutTail <: HList](
  implicit pickEv: Selector.Aux[R, D, RExp],
  addEv: OpInt.Aux[LExp + RExp, OutExp],
  filterEv: FilterNot.Aux[R, FieldType[D, RExp], RTail],
  tailEv: Multiply.Aux[LTail, RTail, OutTail]
): Multiply.Aux[FieldType[D, LExp] :: LTail, R, FieldType[D, OutExp] :: OutTail] =
  new Multiply[FieldType[D, LExp] :: LTail, R] {
    type Out = FieldType[D, OutExp] :: OutTail
}

When the compiler looks for an implicit instance of multiply for FieldType[Metre, -1] :: HNil and FieldType[Metre, 3] :: HNil:

  • It finds that the inductiveCase has a return type which fits the signature
  • Given that the head of the left list is Metre, it selects the exponent for Metre from the right list
  • It can find the evidence addEv to add the exponents -1 and 3
  • It filters Metre from the right list to get HNil
  • It requires evidence of an instance for HNil and HNil
  • This is provided by the base case

The compiler can now find instances of Multiply, as long as a dimension appears in both the left and right lists. This can be extended to when a dimension doesn’t appear by writing a few more inductive cases.

Divide

The reason we went to the effort of writing Invert and Multiply was to divide. Dividing a numerator by a denominator is as simple as inverting the denominator and multiplying it by the numerator. We can write this in a single non-inductive instance:

implicit def divide[L <: HList, R <: HList, RInverted <: HList,
  Divided <: HList](
  implicit invertEv: Invert.Aux[R, RInverted],
  multiplyEv: Multiply.Aux[L, RInverted, Divided]
): Divide.Aux[L, R, Divided] = new Divide[L, R] {
  type Out = Divided
}

That’s far simpler than the work we’ve done before - we’re just building on the typeclasses we wrote to do this.

Automatically derived instances

We can now have compile time dimensional analysis without writing out divide instances for every combination of dimensions:

val rocketMass = Quantity[FieldType[Kilogram, 1] :: HNil](40.0)
val fuelMass = Quantity[FieldType[Kilogram, 1] :: HNil](10000.0)
val exhaust = Quantity[FieldType[Metre, 1] :: FieldType[Second, -1] :: HNil](1000000.0)
val distance = Quantity[FieldType[LightYear, 1] :: HNil](77.0)

val rocketSpeed = times(exhaust, log(divide(add(rocketMass, fuelMass), rocketMass).value))
val time: Quantity[FieldType[Year, 1] :: HNil] = divide(distance, rocketSpeed)
// error: type mismatch; found: FieldType[LightYear, 1] :: FieldType[Metre, -1] :: FieldType[Second, 1] :: HNil; required: FieldType[Year, 1] :: HNil

Yay! We’ve solved the problem! It looks a lot more verbose than what we started with, but we can tidy this up by using extension methods:

implicit final class DoubleOps(val d: Double) {
  def ly: Quantity[FieldType[LightYear,1] :: HNil] = Quantity(d)
  def kg: Quantity[FieldType[Kilogram, 1] :: HNil] = Quantity(d)
  def yr: Quantity[FieldType[Year, 1] :: HNil]     = Quantity(d)
  def mps: Quantity[FieldType[Metre, 1] :: FieldType[Second, -1] :: HNil] = Quantity(d)
  def c: Quantity[FieldType[LightYear, 1] :: FieldType[Year, -1] :: HNil] = Quantity(d)
}

We can also add symbolic infix operators for add, times and divide:

case class Quantity[A](value: Double) {
  def +(q: Quantity[A]): Quantity[A] = Quantity(value + q.value)
  def *(v: Double): Quantity[A] = Quantity(value + v)
  def /[B](q: Quantity[B])(implicit d: Divide[A, B]): Quantity[d.Out] = Quantity(value / q.value)
}

We’ve reached Libra!

We started with doubles:

val rocketSpeed = 1000000.0 * log((40.0 + 10000.0) / 40.0)
val time = 77.0 / rocketSpeed

And we finished with compile time dimensional analysis:

val rocketSpeed = 1000000.0.mps * log(((40.0.kg +10000.0.kg) /40.0.kg).value)
val speedConversion = 300000000.0.mps / 1.c
val speedInC = rocketSpeed / speedConversion
val time = 77.0.ly / speedInC
//time: Quantity[FieldType[Year, 1] :: HNil] = Quantity(4180.65274634)

The code isn’t more verbose - if anything, it’s more explanatory and just as easy to work with.

Rolling this out to more problems

All we need to provide for the business logic of our rocket launch problem are the dimensions and DoubleOps. We could roll this out to any other problem. Let’s say we wanted to do a currency conversion between GBP and DKK:

val exchangeRate: Quantity[FieldType[DKK, 1] :: FieldType[GBP, -1] :: HNil] =
   currentExchangeRate()
Val krone: Quantity[FieldType[DKK, 1] :: HNil] = 10.gbp * exchangeRate

We get dimensional analysis for any problem domain out of the box!

Most of the code we’ve written is library code. In fact, it’s Libra code! Libra is a dimensional analysis library based on typelevel induction. It performs compile time dimensional analysis to any problem domain. It also uses spire for its numeric typeclasses, so can be used for far more than just doubles.

Conclusion

It’s been a long way from the humble Double. We started with basic types, explored dependent types, took a look at Typelevel Scala along the way, before finally ending up performing typelevel induction. As a result, we’ve managed to achieve compile time dimensional analysis for any problem. If you’re curious about typelevel induction take a look at the Libra codebase for more examples. Enjoy!

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

Validated Configurations with Ciris

$
0
0

The need for configuration arises in almost every application, as we want to be able to run in different environments – for example, local, testing, and production environments. Configurations are also used as a way to keep secrets, like passwords and keys, out of source code and version control. By having configurations as untyped structured data in files, we can change and override settings without having to recompile our software.

In this blog post, we’ll take a look at configurations with configuration files, to see how we can make the loading process less error-prone, while overcoming obstacles with boilerplate, testing, and validation. We’ll also identify when it’s suitable to use Scala as a configuration language for improved compile-time safety, convenience, and flexibility; and more specifically, how Ciris helps out.

Configuration Files

Traditionally, configuration files, and libraries like Typesafe Config, have been used to load configurations. This involves writing your configuration file, declaring values and how they’re loaded, and then writing very similar Scala code for loading that configuration. That kind of boilerplate code typically looks something along the lines of the following example.

import com.typesafe.config.{Config, ConfigFactory}

// The settings class, wrapping Typesafe Config
final case class Settings(config: Config) {
  object http {
    def apiKey = config.getString("http.api-key")
    def timeoutSeconds = config.getInt("http.timeout-seconds")
    def port = config.getInt("http.port")
  }
}

// The configuration file, here represented in code
val config =
  ConfigFactory.parseString(
    """
      |http {
      |  api-key = ${?API_KEY}
      |  timeout-seconds = 10
      |  port = 989
      |}
    """.stripMargin
  ).resolve()

val settings = Settings(config)
show(settings)
// Settings(Config(SimpleConfigObject({"http":{"port":989,"timeout-seconds":10}})))

This is a tedious, error-prone process that rarely sees any testing efforts. PureConfig (and other libraries, like Case Classy) were created to remove that boilerplate. Using macros and conventions, they inspect your configuration model (nested case classes) and generate the necessary configuration loading code. This eliminates a lot of errors typically associated with configuration loading. Following is an example of how you can load that very same configuration with PureConfig.

final case class HttpSettings(
  apiKey: String,
  timeoutSeconds: Int,
  port: Int
)

final case class Settings(http: HttpSettings)

val settings = pureconfig.loadConfig[Settings](config)
show(settings)
// Left(ConfigReaderFailures(KeyNotFound("http.api-key", None, Set()), List()))

Encoding Validation

In both previous examples, we do not check whether our configurations are valid to use with our application. In the case of Typesafe Config, we hit a runtime exception if the key is missing or if the type conversion fails, and in PureConfig’s case, we will instead get a ConfigReaderFailures. But in neither case do we care what values are being loaded, as long as they can be converted to the appropriate types. For example, we might require a key of certain length and that it only contains certain characters, the timeout needs to be positive, and the port must be a non-system port number (value in the inclusive range between 1024 and 65535).

You could write an additional validation step to ensure the configuration is valid after it has been loaded – which can be tedious to write and requires testing. One could also argue that the types of the configuration values are too permissive: why use String for the key if you do not accept all String values? And why use an Int for timeout and port, if you only allow a limited subset of values?

We could write these custom types ourselves, including the validation logic, and tell PureConfig how to load them – which would be tedious to write for many types and would require testing. Another alternative is to use refined, which allows you to do type-level refinements (apply predicates) to types. I found this approach so useful that I wrote a small integration between PureConfig and refined at the end of last year (see blog post), so that PureConfig can now load refined’s types.

import eu.timepit.refined.api.Refined
import eu.timepit.refined.numeric.Interval
import eu.timepit.refined.pureconfig._
import eu.timepit.refined.string.MatchesRegex
import eu.timepit.refined.types.numeric.PosInt
import eu.timepit.refined.W

type ApiKey = String Refined MatchesRegex[W.`"[a-zA-Z0-9]{25,40}"`.T]

type NonSystemPort = Int Refined Interval.Closed[W.`1024`.T, W.`65535`.T]

final case class HttpSettings(
  apiKey: ApiKey,
  timeoutSeconds: PosInt,
  port: NonSystemPort
)

final case class Settings(http: HttpSettings)

val settings = pureconfig.loadConfig[Settings](config)
show(settings)
// Left(
//   ConfigReaderFailures(
//     KeyNotFound("http.api-key", None, Set()),
//     List(
//       CannotConvert(
//         "989",
//         "eu.timepit.refined.api.Refined[Int,eu.timepit.refined.boolean.And[eu.timepit.refined.boolean.Not[eu.timepit.refined.numeric.Less[Int(1024)]],eu.timepit.refined.boolean.Not[eu.timepit.refined.numeric.Greater[Int(65535)]]]]",
//         "Left predicate of (!(989 < 1024) && !(989 > 65535)) failed: Predicate (989 < 1024) did not fail.",
//         None,
//         "http.port"
//       )
//     )
//   )
// )

As you can see in the example above, refined already contains type aliases for many common refinement types, like PosInt (for Int values greater than zero). You can also easily define your own predicates, like the one for the key and port. The W here is a shorthand for shapelessWitness: a way to encode literal-based singleton types (essentially, values on the type-level). If you’re using Typelevel Scala with the -Yliteral-types flag, you can write values directly in the type declaration, without having to use Witness.

If you’re not convinced configurations need to be validated, I can recommend reading the paper Early Detection of Configuration Errors to Reduce Failure Damage, and to read through the slides of Leif Wickland’s (one of the authors behind PureConfig) recent presentation Defusing the Configuration Time Bomb on the subject.

In many ways, think of configurations as user input – would you happily accept any values provided to your application from its users? Probably not: you would validate the input, and sanitize it if possible. Think about configurations in the same way, except that the user here might happen to be a developer of the application. The key here, as discussed in the paper linked above, is to check that your configuration is valid as soon as possible, ideally at compile-time, or as soon as the application starts. We want to avoid situations where we’re running the application and suddenly discover that configuration values are invalid or cannot be loaded – or worse, continue running with an invalid configuration, not to discover issues until much later on.

Improving Compile-time Safety

We’ve now got a way to encode validation in the types of our configurations, and a boilerplate-free way of loading values of those types from configuration files – is there still room for improvement? To answer that question, we first need to ask why we are using configuration files in the first place.

Whether you thought about it or not, the main reason for using configuration files is so that we can change settings without having to recompile the software. In my experience, most developers default to using configuration files, and almost always change values by pushing commits to a version control repository. This is followed by a new release of the software, either manually or via a continuous integration system. In scenarios like this, and in general when it’s easy to change and release software (particularly when employing continuous deployment practices), configuration files are not used for the benefit of being able to change values without recompile.

In such cases, why are we not writing the configurations directly in source code? Christopher Vogt has written an excellent blog post (and given a presentation) on the subject. The tricky part here is managing values which need to be dynamic in the environment (like the port to bind) and are secret (like passwords and keys). Depending on your requirements and preferences, you more or less have two alternatives.

  • If you know which environments your application will run in, and what the configuration values will be in those environments, you can just include the configurations in your application code (if it has no secrets), or store, compile, and bundle the configuration separately. If you have a requirement that secrets shouldn’t touch persistent storage, this might not be a feasible alternative. You might also appreciate the fact that all code relating to your application is in the same version control repository and gets compiled together, in which case this approach might not be suitable.

  • Alternatively, you can include the configuration in your application, but load secrets and values which need to be dynamic from the environment during runtime. This is necessary when configuration values cannot be determined beforehand – because you do not know what environment your application will run in, or if you use a vault (like credstash, for example) or a configuration service (like ZooKeeper, for example) – or if you prefer having your configuration together with your application code and in the same version control repository.

In this post, we’ll only focus on the latter case. While it’s possible to not use any libraries in the latter case, loading values from the environment typically means dealing with: different environments and configuration sources, type conversions, error handling, and validation. This is where Ciris comes in: a small library, dependency-free at its core, helping you to deal with all of that more easily.

Introducing Ciris

Imagine for the moment that no part of your configuration is secret and that your application only ever runs in one environment. You can then just write your configuration in code.

import eu.timepit.refined.auto._

final case class Config(
  apiKey: ApiKey,
  timeoutSeconds: PosInt,
  port: NonSystemPort
)

val config =
  Config(
    apiKey = "RacrqvWjuu4KVmnTG9b6xyZMTP7jnX",
    timeoutSeconds = 10,
    port = 4000
  )

You then realize that it’s a bad idea to put the key in the source code, because source code can easily get into the wrong hands. You decide that you’ll instead read an environment variable for the key. Since you want to make sure that your configuration is valid, you have used refinement types, so you’ll have to make sure to check that the key conforms to the predicate. You would also welcome a helpful error message if the key is missing or invalid. This sounds like more work than it should be, so let’s see how Ciris can help us.

Ciris method for loading configurations is loadConfig and it works in two steps: first define what to load, and then how to load the configuration. For reading a key from an environment variable, you can use env[ApiKey]("API_KEY") which reads the environment variable API_KEY as an ApiKey. Ciris has a refined integration in a separate module, so you just need to add an appropriate import. Loading the configuration is then just a function accepting the loaded values as arguments.

import ciris._
import ciris.refined._

val config =
  loadConfig(
    env[ApiKey]("API_KEY")
  ) { apiKey =>
    Config(
      apiKey = apiKey,
      timeoutSeconds = 10,
      port = 4000
    )
  }
show(config)
// Left(ConfigErrors(MissingKey(API_KEY, Environment)))

Ciris deals with type conversions, error handling, and error accumulation, so you can focus on your configuration. The loadConfig method returns an Either[ConfigErrors, T] instance back, where T is the result of your configuration loading function. You can retrieve the accumulated error messages by using messages on ConfigErrors.

show { config.left.map(_.messages) }
// Left(Vector("Missing environment variable [API_KEY]"))

If we decided that the port needs to be dynamic as well, we can simply make that change. In the example below, we are using prop to read the http.port system property for the port to use. As you can see, you are free to mix configuration sources as you please. While we are reading environment variables and system properties in these examples, you could just as well use sources for some configuration services or vaults.

val config =
  loadConfig(
    env[ApiKey]("API_KEY"),
    prop[NonSystemPort]("http.port")
  ) { (apiKey, port) =>
    Config(
      apiKey = apiKey,
      timeoutSeconds = 10,
      port = port
    )
  }
show { config.left.map(_.messages) }
// Left(
//   Vector(
//     "Missing environment variable [API_KEY]",
//     "Missing system property [http.port]"
//   )
// )

You might recognize the similarities between loadConfig and ValidatedNel with an Apply instance from Cats. That’s because it’s more or less how loadConfig works behind the scenes, except Ciris has its own custom implementation in order to be dependency-free in the core module.

Multiple Environments

We still have to deal with multiple environments in our configuration, assuming there are differences between configurations, or how they are loaded, in the different environments. There are several ways you can do this with Ciris – one way is to define an enumeration with enumeratum and load values of that enumeration. Let’s say we want to use a default configuration when running the application locally, but want to keep the key and port dynamic in the other environments (testing and production). We start by defining an enumeration of the different environments.

import _root_.enumeratum._

object environments {
  sealed abstract class AppEnvironment extends EnumEntry
  object AppEnvironment extends Enum[AppEnvironment] {
    case object Local extends AppEnvironment
    case object Testing extends AppEnvironment
    case object Production extends AppEnvironment

    val values = findValues
  }
}

We can use the withValue method to define a requirement on a configuration value in order to be able to load our configuration. It works just like loadConfig, except it wraps your loadConfig statements (think of it as flatMap, while loadConfig is map). If no environment was specified in the environment variable APP_ENV or if it was set to Local, we will use a default configuration. We’ll load the configuration just like before for any other valid environments (testing and production).

import environments._
import ciris.enumeratum._

val config =
  withValue(env[Option[AppEnvironment]]("APP_ENV")) {
    case Some(AppEnvironment.Local) | None =>
      loadConfig {
        Config(
          apiKey = "RacrqvWjuu4KVmnTG9b6xyZMTP7jnX",
          timeoutSeconds = 10,
          port = 4000
        )
      }

    case _ =>
      loadConfig(
        env[ApiKey]("API_KEY"),
        prop[NonSystemPort]("http.port")
      ) { (apiKey, port) =>
        Config(
          apiKey = apiKey,
          timeoutSeconds = 10,
          port = port
        )
      }
  }
show(config)
// Right(Config(RacrqvWjuu4KVmnTG9b6xyZMTP7jnX, 10, 4000))

An alternative to the above is to have multiple entrypoints (main methods) in your application, each running the application with different configuration loading code (or using a default configuration) for the respective environment. Depending on how packaging and running of your application looks like across different environments, this may or may not be a suitable solution. Note that it’s very much possible to mix these approaches, and you should strive to find what works best in your case.

// Runs the application with the provided configuration
def runApplication(config: Config): Unit = { /* omitted */ }

object Local {
  def main(args: Array[String]): Unit =
    runApplication {
      Config(
        apiKey = "RacrqvWjuu4KVmnTG9b6xyZMTP7jnX",
        timeoutSeconds = 10,
        port = 4000
      )
    }
}

object TestingOrProduction {
  def main(args: Array[String]): Unit =
    runApplication {
      val config =
        loadConfig(
          env[ApiKey]("API_KEY"),
          prop[NonSystemPort]("http.port")
        ) { (apiKey, port) =>
          Config(
            apiKey = apiKey,
            timeoutSeconds = 10,
            port = port
          )
        }

      config.fold(
        errors => throw new IllegalArgumentException(s"Unable to load configuration: ${errors.messages}"),
        identity
      )
    }
}

Testing Configurations

Writing your configurations in Scala means you have the flexibility to work with them as you want. You’re no longer limited to what can be done with configuration files. Sharing configurations between your application and tests is also very straightforward – simply make the configuration loading function (and the default configuration) available for the tests.

// This can now be accessed from the tests
val defaultConfig =
  Config(
    apiKey = "RacrqvWjuu4KVmnTG9b6xyZMTP7jnX",
    timeoutSeconds = 10,
    port = 4000
  )

// This can now be accessed from the tests
val configWith =
  (apiKey: ApiKey, port: NonSystemPort) =>
    Config(
      apiKey = apiKey,
      timeoutSeconds = 10,
      port = port
    )

val config =
  withValue(env[Option[AppEnvironment]]("APP_ENV")) {
    case Some(AppEnvironment.Local) | None =>
      loadConfig(defaultConfig)
    case _ =>
      loadConfig(
        env[ApiKey]("API_KEY"),
        prop[NonSystemPort]("http.port")
      )(configWith)
  }

If you really want to unit test the configuration loading as well, you can do so with minor rewrites. Currently, we depend on some fixed configuration sources for environment variables and system properties (technically, system properties are mutable), but if we instead pass sources (ConfigSources) as arguments, we can read values from those sources using the read method.

The read method normally looks for an implicit ConfigSource to read from, which would have been perfect if we only used a single source. But since we have multiple sources here, we instead use read to redefine env and prop to read from the provided sources. ConfigReader[T] captures the ability to convert from String to T, where the String value has been read from a ConfigSource.

def config(
  envs: ConfigSource[String],
  props: ConfigSource[String]
): Either[ConfigErrors, Config] = {
  // Custom env which reads from envs
  def env[T: ConfigReader](key: String) =
    read[T](key)(envs, ConfigReader[T])

  // Custom prop which reads from props
  def prop[T: ConfigReader](key: String) =
    read[T](key)(props, ConfigReader[T])

  withValue(env[Option[AppEnvironment]]("APP_ENV")) {
    case Some(AppEnvironment.Local) | None =>
      loadConfig(defaultConfig)
    case _ =>
      loadConfig(
        env[ApiKey]("API_KEY"),
        prop[NonSystemPort]("http.port")
      )(configWith)
  }
}

We’ll then define a couple of helper methods for creating ConfigSources from key-value pairs. The ConfigSource type parameter is the type of keys the source can read, which is String for both environment variables and system properties. The ConfigKeyType is basically the name of the key that can be read, for example environment variable. Below we’re using predefined instances in the ConfigKeyType companion object.

def envs(entries: (String, String)*): ConfigSource[String] =
  ConfigSource.fromMap(ConfigKeyType.Environment)(entries.toMap)

def props(entries: (String, String)*): ConfigSource[String] =
  ConfigSource.fromMap(ConfigKeyType.Property)(entries.toMap)

We can test our config method using different combinations of environment variables and system properties. Note that envs and props have the same type, so if you want to avoid using them interchangeably, you can define custom wrapper types for them. We’ll leave that out here for sake of simplicity. I’ve found that it’s not very common to read values from more than one ConfigSource, but as it’s definitely possible, it can be worth making sure you do not mix them up.

show { config(envs(), props()) }
// Right(Config(RacrqvWjuu4KVmnTG9b6xyZMTP7jnX, 10, 4000))

show {
  config(
    envs("APP_ENV" -> "Local"),
    props()
  )
}
// Right(Config(RacrqvWjuu4KVmnTG9b6xyZMTP7jnX, 10, 4000))

show {
  config(
    envs("APP_ENV" -> "QA"),
    props()
  ).left.map(_.messages)
}
// Left(
//   Vector(
//     "Environment variable [APP_ENV] with value [QA] cannot be converted to type [$line34.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$environments$AppEnvironment]"
//   )
// )

show {
  config(
    envs("APP_ENV" -> "Production"),
    props()
  ).left.map(_.messages)
}
// Left(
//   Vector(
//     "Missing environment variable [API_KEY]",
//     "Missing system property [http.port]"
//   )
// )

show {
  config(
    envs(
      "APP_ENV" -> "Production",
      "API_KEY" -> "changeme"
    ),
    props()
  ).left.map(_.messages)
}
// Left(
//   Vector(
//     "Environment variable [API_KEY] with value [changeme] cannot be converted to type [eu.timepit.refined.api.Refined[String,eu.timepit.refined.string.MatchesRegex[java.lang.String(\"[a-zA-Z0-9]{25,40}\")]]]: Predicate failed: \"changeme\".matches(\"[a-zA-Z0-9]{25,40}\").",
//     "Missing system property [http.port]"
//   )
// )

show {
  config(
    envs(
      "APP_ENV" -> "Production",
      "API_KEY" -> "X9aKACPtircCrrFKYhwPr7fXx8srow"
    ),
    props()
  ).left.map(_.messages)
}
// Left(Vector("Missing system property [http.port]"))

show {
  config(
    envs(
      "APP_ENV" -> "Production",
      "API_KEY" -> "X9aKACPtircCrrFKYhwPr7fXx8srow"
    ),
    props("http.port" -> "900")
  ).left.map(_.messages)
}
// Left(
//   Vector(
//     "System property [http.port] with value [900] cannot be converted to type [eu.timepit.refined.api.Refined[Int,eu.timepit.refined.numeric.Interval.Closed[Int(1024),Int(65535)]]]: Left predicate of (!(900 < 1024) && !(900 > 65535)) failed: Predicate (900 < 1024) did not fail."
//   )
// )

show {
  config(
    envs(
      "APP_ENV" -> "Production",
      "API_KEY" -> "X9aKACPtircCrrFKYhwPr7fXx8srow"
    ),
    props("http.port" -> "4000")
  )
}
// Right(Config(X9aKACPtircCrrFKYhwPr7fXx8srow, 10, 4000))

Finally, when running the application, simply provide the actual ConfigSources for environment variables and system properties.

show { config(ConfigSource.Environment, ConfigSource.Properties) }
// Right(Config(RacrqvWjuu4KVmnTG9b6xyZMTP7jnX, 10, 4000))

Conclusion

In this blog post, we’ve seen how we can make the configuration loading process, with configuration files, less error-prone, by eliminating the boilerplate code with PureConfig, and encoding validation with refined – seeing how the two libraries can work together seamlessly.

We’ve also identified cases where we can use Scala as a configuration language, seeing that it’s particularly suitable in cases where it’s easy to change and deploy software. We’ve introduced the challenge of loading configuration values from the environment and seen how Ciris can help you with that, letting you focus on the configuration. We’ve seen that Scala configurations can provide more compile-time safety and flexibility than traditional configurations with configuration files.

If you’re looking for more information on Ciris, the project’s website (https://cir.is) is a good start.
There’s also a usage guide and API documentation which expands on what’s been discussed here.

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

Announcement: cats 1.0.0-MF

$
0
0

The cats maintainer team is proud to announce the cats 1.0.0-MF release.

MF stands for milestone final, this will be the last release before cats 1.0.0-RC1 which will be followed by cats 1.0 shortly.

The main purpose/focus of this release is to offer a relatively stable API to work with prior to the official 1.0. It can be deemed as a proposal for the final cats 1.0 API. Please help test it and report any improvements/fixes needed either in the cats-dev gitter channel or as github issues. Post cats 1.0, we will keep API stable and maintain strong binary compatibility.

Highlights of the major new features include but not limited to:

  • #1117: Stack safe foldLeftM without Free, by @TomasMikula
  • #1598: A ReaderWriterStateT data type, by @iravid
  • #1526 and #1596: InjectK for free programs, by @tpolecat and @andyscott
  • #1602: Stack-safe Coyoneda, by @edmundnoble
  • #1728: As class which represents subtyping relationships (Liskov), by @stew
  • #1178: Is constructor for Leibniz equality, by @tel
  • #1748: Stack-safe FreeApplicative, by @edmundnoble
  • #1611: NonEmptyTraverse. by @LukaJCB

Overall 1.0.0-MF has over 120 merged pull requests of API additions, bug fixes, documentation and misc improvements from 44 contributors. For the complete change list please go to the release notes.

Migration

There are more breaking changes in this release - we want to include as many necessary breaking changes as possible in this release to reach stability. Please follow the migration guide from 0.9.0 in the release notes.

What’s next

Although we made many improvements to the documentation in this release, it’s still by and large a WIP. The next release 1.0.0-RC1 will focus documentation and API refinement based on community feedback. RC1 is scheduled to be released in September. Unless the amount of bug fixes warrants a RC2, it’s likely that we’ll release cats 1.0.0 within a couple weeks after RC1.

Credits

Last but not least, many thanks to the contributors that make this release possible:

  • @alexandru
  • @andyscott
  • @BenFradet
  • @Blaisorblade
  • @cb372
  • @ceedubs
  • @cranst0n
  • @DavidGregory084
  • @denisftw
  • @DieBauer
  • @diesalbla
  • @djspiewak
  • @durban
  • @edmundnoble
  • @iravid
  • @jtjeferreira
  • @julien-truffaut
  • @jyane
  • @kailuowang
  • @larsrh
  • @Leammas
  • @leandrob13
  • @LukaJCB
  • @markus1189
  • @milessabin
  • @n4to4
  • @oskoi
  • @peterneyens
  • @PeterPerhac
  • @raulraja
  • @RawToast
  • @sellout
  • @stew
  • @sullivan-
  • @SystemFw
  • @takayuky
  • @tel
  • @TomasMikula
  • @tpolecat
  • @wedens
  • @xavier-fernandez
  • @xuwei-k
  • @yilinwei
  • @zainab-ali

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

There are at least three types of strings

$
0
0

Newtype mechanisms are a great way to introduce wrapper-free, global distinctions of “different” instances of the same type. But we can do that on a local level, too, by using type parameters.

Consider these two signatures.

def mungeIDs(uids: List[String], gids: List[String],
             oids: List[String]): Magic[String, String, String]

def mungeIDsSafely[UID <: String, GID <: String, OID <: String]
            (uids: List[UID], gids: List[GID],
             oids: List[OID]): Magic[UID, GID, OID]

The second function is a strictly more general interface; the first, concrete signature can be implemented by calling the second function, passing [String, String, String] as the type arguments. There is no need to even have the first signature; anywhere in your program where you pass three List[String]s as arguments to mungeIDsSafely, the proper type arguments will be inferred.

Yet, assuming you don’t wish mungeIDs to be oracular (i.e. a source of UIDs, GIDs, and OIDs), the second signature is probably much more reliable, because type parameters are quite as mysterious as the opaque abstract type members of the newtype mechanism.

  1. mungeIDsSafely can’t invent new IDs, not even with null.
  2. It can’t combine them to produce new IDs.
  3. It can treat the three list arguments as List[String]. However, it cannot convert any String back into an ID; any UIDs, GIDs, or OIDs that appear in the result Magic[UID, GID, OID] must have come from one of the argument lists, directly. (That’s not to say that mungeIDsSafely can’t use the string-nature to make that decision; for example, it could always choose the smallest-as-string UID to put into the resulting Magic. But, that UID is still enforced to be a proper element of the uids argument, and cannot be gotten from anywhere else.
  4. Perhaps most importantly, it cannot mix up UIDs, GIDs, and OIDs. Even though, “really”, they’re all strings!

It is entirely irrelevant that you cannot subclass String in Scala, Java, or whatever. There are more types than classes.

Given the advantages, it’s very unfortunate that the signature of mungeIDsSafely is so much noisier than that of mungeIDs. At least you have the small consolation of eliminating more useless unit tests.

This is a good first approximation at moving away from the dangers of concreteness in Scala, and has the advantage of working in Java, too (sort of; the null prohibition is sadly relaxed).

Non-supertype constraints

In Scala, you can also use implicits to devise arbitrary constraints, similar to typeclasses in Haskell, and sign your functions using implicits instead, for much finer-grained control, improved safety, and types-as-documentation.

// a typeclass for "IDish types" (imagine instances)
sealed trait IDish[A]

def mungeIDsTCey[UID: IDish, GID: IDish, OID: IDish]
            (uids: List[UID], gids: List[GID],
             oids: List[OID]): Magic[UID, GID, OID]

Though all three types have the same constraint, IDish, they are still distinct types. And now, the coupling with String is broken; as the program author, you get to decide whether you want that or not.

Pitfalls avoided for you

Luckily, Java doesn’t make the mistake of “reified generics”. If it did, you could ask whether UID = GID = OID = String, and all your safety guarantees would be gone. Forcing all generics to be reified does not grant you any new expressive power; all it does is permanently close off large swaths of the spectrum of mystery to you, forbidding you from using the full scope of the design space to improve the reliability of your well-typed programs.

The same goes for claiming that null ought to be a default member of every type, even the abstract ones that ought to be a little more mysterious; it’s easy to add new capabilities (e.g. Scala’s >: Null constraint, if you really must use null), but taking them away is much, much harder.

Furthering this spirit of making good programs easier to write and bad programs harder to write, a useful area of research in Scala might be making signatures such as that of mungeIDsSafely nicer, or signatures such as that of mungeIDs uglier.

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.


Who implements the typeclass instance?

$
0
0

The typeclass pattern in Scala invites you to place implementation-specific knowledge directly in the typeclass instances, with the interface defined as the typeclass’s abstract interface.

However, GADTs permit a different organization of code. It is even possible to define a typeclass that seems to do nothing at all, yet still permits full type-safe typeclass usage.

The possibilities between these two extremes form a design space. If you wish to practice ad-hoc polymorphism in Scala, this space is well worth exploring.

A glorified overloader

Refactoring a set of overloads into a typeclass is a fine way to get some free flexibility and dynamism, because expressing overloads as a typeclass gives you free fixes for common overload problems.

  1. Methods calling the overloaded method do not themselves need to be overloaded just to avoid suppressing the flexibility of the overload beneath. (See addThree and zipAdd below for examples.)
  2. Return-type overloading works, even in Scala, where it does not when attempting to write overloads in the Java style, i.e. multiple methods with the same name.
  3. Overloads may be defined as recursive type rules, admitting a combinatorial explosion or even infinite “effective overloads”.

Let’s make a quick example of something like a typical overload.

object OverAdd {
  def add(x: Int, y: Int): Int = x + y

  def add(x: String, y: String): String = s"$x$y"

  def add[A](l: Vector[A], r: Vector[A]): Vector[A] =
    l ++ r
}

This mechanically translates to a newly introduced type, some implicit instances of that type, and a function to let us call add the same way we used to.

// typeclasses are often defined with trait, but this is not required
final case class Adder[A](addImpl: (A, A) => A)

// easier if all implicits are in this block
object Adder {
  implicit val addInts: Adder[Int] = Adder((x, y) => x + y)

  implicit val addStrings: Adder[String] =
    Adder((x, y) => s"$x$y")

  implicit def addVects[A]: Adder[Vector[A]] =
    Adder((l, r) => l ++ r)
}

// and to tie it back together
def add[A](x: A, y: A)(implicit adder: Adder[A]): A =
  adder.addImpl(x, y)

Overloaded wrapping without overloading

While a bit more ceremonious, this allows us to write some nice functions more easily. Here’s a function to add three values.

def addThree[A: Adder](l: A, m: A, r: A): A =
  add(l, add(m, r))

addThree supports all three “overloads” of add.

scala> addThree(1, 2, 3)
res0: Int = 6

scala> addThree("a", "ba", "cus")
res1: String = abacus

scala> addThree(Vector(1, 1), Vector(0, 0), Vector(1, 0, 0, 1))
res2: scala.collection.immutable.Vector[Int] =
  Vector(1, 1, 0, 0, 1, 0, 0, 1)

With the overload style, we need three variants of this function, too, each with the exact same body. The typeclass version need only be written once, and automatically supports new overloads, that is, new instances of Adder.

Same with this function.

def zipAdd[A: Adder](l: List[A], r: List[A]): List[A] =
  l zip r map {case (x, y) => add(x, y)}

Functions like addThree and zipAdd are called derived combinators. The more that you can do in derived combinators, the more abstract and orthogonal your program will be.

   +=============+            |   +=============+
   |   derived   |  (open     |   |  primitive  |  (closed
   | combinators |    set)    |   | combinators |      set)
   +=============+            |   +=============+
                              |
   +----------+               |    +----------+
   | addThree |---→---→---→---→---→| Adder    |
   +----------+       calls   |    | -addImpl |    +===========+
    ↑                       |→---→ +----------+    | Instances |
    | +--------+            | |                    +===========+
    | | zipAdd |---→---→---→- |
    | +--------+    calls     |  +------+ +---------+ +-------+
    ↑        ↑                |  | Ints | | Strings | | Vects |
    |   calls|                |  +------+ +---------+ +-------+
    |      +-----+            |      |
    |      | ??? |            |      |
    ↑      +-----+            |      |
    |  (derived combinators          ↓
    |   can derive from each other)  ---→---→---→
    |                                           |
    ↑          -------------------------------  |
    |          To evaluate `addThree(1, 2, 3)`  |
    |          -------------------------------  ↓
    |          1. Fetch `Adder` implicitly      |
    |-←---←---←---←---←---←---←---←---←---←---←-|
               2. Pass to `addThree`
               3. `addThree` uses the abstract interface to
                  invoke the primitive `add` combinator on what,
                  to it, is an abstract type, `A`.

Infinite overloads via recursion

Making derived combinators easier to write is very useful, but typeclasses go further by letting you describe overloading rules that would be impossible with normal overloading.

Given that I can add Int and Int together, I should be able to add (Int, Int) and (Int, Int) to get (Int, Int).

implicit val addIntPairs: Adder[(Int, Int)] =
  Adder{case ((x1, x2), (y1, y2)) =>
    (x1 + y1, x2 + y2)}

scala> add((2, 7), (3, 8))
res3: (Int, Int) = (5,15)

But I should also be able to add pairs of String. And (Int, String) pairs. And (String, Vector[Boolean]) pairs. And pairs of pairs of pairs.

Typeclasses let you declare newly supported types recursively, with an implicit argument list to the implicit def.

implicit def addPairs[A: Adder, B: Adder]
    : Adder[(A, B)] =
  Adder{case ((a1, b1), (a2, b2)) =>
    (add(a1, a2), add(b1, b2))
  }

Surely this must be going somewhere new

If you’re familiar with type classes, all this must be old hat. But this time, we’re going to expand the boundaries of the typeclass design space, by exploiting GADT pattern matching.

We could have designed the Adder type class to include addThree as a primitive combinator, and implemented it afresh for each of the four instances we’ve defined so far, as well as any future instances someone might define. Thinking orthogonally, however, shows us that there’s a more primitive concept which strictly generalizes it: if we primitively define a two-value adder, we can use it to add three items, simply by using it twice.

This has a direct impact on how we structure the functions related to Adder. The primitives must be split up, their separate implementations appearing directly in the implicit instances. Derived combinators may occur anywhere that is convenient to us: outside the typeclass for full flexibility of location, or within the typeclass for possible overrides for performance.

But how much of the primitive implementations must occur in the instances, really?

Empty tags as instances

There is a progression of design refinements here.

  1. Ad hoc overloads, Java-style, impossible to abstract over.
  2. Flip into a typeclass.
  3. Refine the primitive/derived distinction to minimize code in instances.

For some typeclasses, no code needs to be put in the instances. For example, if we want to support only Int, String, and Vector, here is a perfectly sensible typeclass definition.

sealed trait ISAdder[A]

object ISAdder {
  implicit object AddInts extends ISAdder[Int]
  implicit object AddStrs extends ISAdder[String]

  final case class AddVects[E]() extends ISAdder[Vector[E]]

  implicit def addVects[E]: ISAdder[Vector[E]] =
    AddVects()
}

If the instances cannot add values of the types indicated by the type parameters, surely that code must exist somewhere! And it has a place, in the definition of add.

If you recall, this method merely called addImpl on the typeclass instance before. Now there is no such thing; the instances are empty.

Well, they are not quite empty; they contain a type. So we can define add, with complete type safety, as follows.

def isadd[A](x: A, y: A)(implicit adder: ISAdder[A]): A =
  adder match {
    case ISAdder.AddInts => x + y
    case ISAdder.AddStrings => s"$x$y"
    case ISAdder.AddVects() =>
      x ++ y
  }

More specifically, they contain a runtime tag, which allows information about the type of A to be extracted with a pattern match. For example, determining that adder is AddInts reveals that A = Int, because that’s what the extends clause says. This is GADT pattern matching.

The Vector case is a little tricky here, because we can only determine that A is Vector[e] for some unknown e, but that’s enough information to invoke ++ and get a result also of Vector[e] for the same e.

You can see this in action by using a variable type pattern to assign the name e (a lowercase type parameter is required for this usage), so you can refer to it in types.

    case _: ISAdder.AddVects[e] =>
      (x: Vector[e]) ++ y

The lowercase e names a GADT skolem

AddVects[e] pattern we’ve been discussing, e is a variable type pattern. This is a type that exists only in the scope of the case.

It’s existential because we don’t know what it is, only that it is some type and we don’t get to pick here what that is. In this way, it is no different from a type parameter’s treatment by the implementation, which is existential on the inside.

It’s a GADT skolem because it was bound by the pattern matching mechanism to a “fresh” type, unequal to any other. Recall the way AddVects was defined:

AddVects[E] extends ISAdder[Vector[E]]

Matching ISAdder with AddVects doesn’t tell us anything about bounds on the type passed to AddVects at construction time. This isn’t true of all GADT skolems, but is only natural for this one.

scalac will create this GADT skolem regardless of whether we give it a name. In the pattern case AddVects(), it’s still known that A = Vector[e] for some e; the only difference is that you haven’t bound the e name, so you can’t actually refer to this unspeakable type.

Usually, you do not need to assign names such as e to such types; _ is sufficient. However, if you have problems getting scalac to apply all the type equalities it ought to know about, a good first step is to assign names to any skolems and try type ascriptions. You’ll need a variable type pattern in other situations that don’t infer, too. By contrast, with the e name bound, we can confirm that x: Vector[e] in the above example, and y is sufficiently well-typed for the whole expression to type-check.

Porting addPairs and other recursive cases

Suppose we add support for pairs to ISAdder.

final case class AddPairs[A, B](
    val fst: ISAdder[A],
    val snd: ISAdder[B]
  ) extends ISAdder[(A, B)]

This should permit us to pattern-match in isadd to make complex determinations about the A type given to isadd. This ought to be a big win for GADT-style typeclasses, allowing “short-circuiting” patterns that work in an obvious way.

// this pattern means A=(Int, String)
case AddPairs(AddInts, AddStrs) =>

// this pattern means A=(ea, Vector[eb])
// where ea and eb are GADT skolems
case AddPairs(fst, _: AddVects[eb]) =>

// here, A=(ea, eb) (again, GADT skolems)
// calling `isadd` recursively is the most
// straightforward implementation
case AddPairs(fst, snd) =>
  val (f1, s1) = x
  val (f2, s2) = y
  (isadd(f1, f2)(fst), isadd(s1, s2)(snd))

The final case’s body is fine. scalac effectively introduces skolems ea and eb so that A = (ea, eb), fst: Adder[ea], and so on, and everything lines up nicely. We are not so lucky with the other cases.

....scala:76: pattern type is incompatible with expected type;
 found   : ISAdder.AddInts.type
 required: ISAdder[Any]
      case AddPairs(AddInts, AddStrs) =>
                    ^
....scala:76: pattern type is incompatible with expected type;

 found   : ISAdder.AddStrs.type
 required: ISAdder[Any]
      case AddPairs(AddInts, AddStrs) =>
                             ^
....scala:79: pattern type is incompatible with expected type;
 found   : ISAdder.AddVects[eb]
 required: ISAdder[Any]
      case AddPairs(fst, _: AddVects[eb]) =>
                            ^

This is nonsensical; the underlying code is sound, we just have to go the long way around so that scalac doesn’t get confused. Instead of the above form, you must assign names to the AddPairs skolems as we described above, and do a sub-pattern-match.

case p: AddPairs[ea, eb] =>
  val (f1, s1) = x
  val (f2, s2) = y
  (p.fst, p.snd) match {
    case (AddInts, AddStrs) =>
    case (fst, _: AddVects[eb]) =>
    case (fst, snd) =>

Note that we had to give up on the AddPairs pattern entirely, because

  1. More complex situations require type ascription.
  2. You cannot ascribe with skolems unless you’ve bound the skolems to names with type variable patterns.
  3. You can’t use type variable patterns with the structural “ADT-style” patterns; you must instead use inelegant and inconvenient variable type patterns.

Yet this remains entirely up to shortcomings in the current pattern matcher implementation. An improved pattern matcher could make the nice version work, safely and soundly.

As such, I don’t want these shortcomings to discourage you from trying out the pure type-tagging, “GADT-style” typeclasses. It is simply nicer for many applications, and you aren’t going to code yourself into a hole with them, because should you wind up in the buggy territory we’ve been exploring, there’s still a way out.

Same typeclass, new “primitive” combinators

“Empty” typeclasses like ISAdder contain no implementations of primitive combinators, only “tags”. As such, they are in a sense the purest form of “typeclass”; to classify types is the beginning and end of what they do!

Every type that is a member of the “class of types” ISAdder is either

  1. the type Int,
  2. the type String,
  3. a type Vector[e], where e is any type, or
  4. a type (x, y) where x and y are types that are also in the ISAdder class.

This is the end of ISAdder’s definition; in particular, there is nothing here about “adding two values to get a value”. All that matters is what types are in the class!

Given this ‘undefinedness’, if we have another function we want to write over the exact same class-of-types, we can just write it without making any changes to ISAdder.

def backwards[X](x: X)(implicit adder: ISAdder[X]): X = adder match {
  case AddInts => -x
  case AddStrs => x.reverse
  case _: AddVects[e] => x.reverse
  case p: AddPairs[ea, eb] =>
    val (a, b): (ea, eb) = x
    (backwards(a)(p.fst), backwards(b)(p.snd))
}

Set aside the question of whether the class of “backwards-able” types ought to remain in lockstep with the class of “addable” types. Supposing that it should, the class need be defined only once.

More practically speaking, if you expose the subclasses of a typeclass to users of your library, they can define primitives “in lockstep”, too. The line between primitive and derived combinators is also blurred: a would-be derived combinator can pattern-match on the typeclass to supply special cases for improved performance, becoming “semi-primitive” in the process. You decide whether these are good things or not.

Hybrid “clopen” typeclasses

Pattern matching type class GADTs is subject to the same exhaustiveness concerns and compiler warnings as pattern matching ordinary ADTs. If you eliminate a case from def isadd, you’ll see something like

....scala:57: match may not be exhaustive.
It would fail on the following input: AddInts
    adder match {
    ^

We could unseal ISAdder, which would eliminate the warning, but wouldn’t really solve anything. The function would still crash upon encountering the missing case.

Pattern matches of unsealed hierarchies typically include a “fallback” case, code used when none of the “special” cases match. However, for pure typeclasses like ISAdder, this strategy is a dead end too. Consider a hypothetical fallback case.

  case _ => ???

Each of the other patterns in isadd, by their success, taught us something useful about the A type parameter. For example, case AddInts tells us that A = Int, and accordingly x: Int and y: Int. It also meant that the expected result type of that block is also Int. That’s plenty of information to actually implement “adding”.

By contrast, case _ tells us nothing about the A type. We don’t know anything new about x, y, or the type of value we ought to return. All we can do is return either x or y without further combination; while this is a sort of “adding” in abstract algebra, there’s a good chance it’s not really what the caller was expecting.

Instead, we can reformulate a closed typeclass like ISAdder with one extension point, where the typeclass is specially encoded in the usual “embedded implementation” style. It’s closed and open, so “clopen”.

sealed doesn’t seal subclasses

Our GADT typeclass instances work by embedding type information within the instances, to be rediscovered at runtime. To support open extension, we need a data case that contains functions instead of types. We know how to encode that, because that is how standard, non-GADT typeclasses work.

sealed trait ISOAdder[A]

trait ExtISOAdder[A] extends ISOAdder[A] {
  val addImpl: (A, A) => A
}

object ISOAdder {
  implicit object AddInts extends ISOAdder[Int]
  implicit object AddStrs extends ISOAdder[String]

  final class AddVects[A] extends ISOAdder[Vector[A]]

  implicit def addVects[A]: ISOAdder[Vector[A]] =
    new AddVects

  def isoadd[A](x: A, y: A)(implicit adder: ISOAdder[A]): A =
    adder match {
      case AddInts => x + y
      case AddStrs => s"$x$y"
      case _: AddVects[e] =>
        (x: Vector[e]) ++ y
      // NB: no unchecked warning here, which makes sense
      case e: ExtISOAdder[A] =>
        e.addImpl(x, y)
    }
}

By sealing ISOAdder, we ensure that the pattern match in isoadd remains exhaustive. However, one of those cases, ExtISOAdder, admits new subclasses, itself! This is fine because no matter how many subclasses of ExtISOAdder we make, they’ll still match the last pattern of isoadd.

We could also define ExtISOAdder as a final case class. The point is that you can make this “extension point” in your otherwise-closed typeclass using whatever style you like.

One caveat, though: “clopen” typeclasses cannot have arbitrary new primitive combinators added to them. They are like ordinary open typeclasses in that regard. Consider a version of backwards for ISOAdder: what you could do in the ExtISOAdder case?

Whoever you like

With type parameters vs. members, you can get pretty far with the “rule of thumb”. Beyond that, even bugs in scalac typechecking can guide you to the “right” choice.

There is no similar rule for this design space. It might seem that typeclass newcomers might have an easier time with the OO-style “unimplemented method” signposts in the open style, but I have also seen them lament the loss of flexibility that would be provided by the GADT style.

Likewise, as an advanced practitioner, your heart will be rent by the tug-of-war between the boilerplate of the open style and the pattern-matcher’s finickiness with the GADT style. You may then be tempted to adopt the hybrid ‘clopen’ style, but this, too, is too often a form of design excess.

Given all that, the only help I can offer, aside from describing the design space above, is “pick whichever you like”. You know your program; if you are not sure which will be nicer, try both!

This article was tested with Scala 2.12.4.

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

Announcement: cats 1.0.0

$
0
0

The cats maintainer team is proud to announce the cats 1.0.0 release. Cats has been striving to provide functional programming abstractions that are core, modular, approachable and efficient. Cats 1.0.0 marks the point where we believe that our API is robust and stable enough to start guarantee backward binary compatibility going forward until Cats 2.0. We expect the Cats 1.x series to be fully backwards compatible for at least one year. This is a major milestone towards our goal of providing a solid foundation for an ecosystem of pure, typeful functional libraries.

Migration

The vast majority of changes since 1.0.0-RC1 are API compatible, with scalafix scripts ready for those that do not. Here is the change list and migration guide.

Binary compatibility

After 1.0.0 release, we’ll use the MAJOR.MINOR.PATCH Semantic Versioning 2.0.0 going forward, which is different from the EPOCH.MAJOR.MINOR scheme common among Java and Scala libraries (including the Scala lang). In this semantic versioning, backward breaking change is ONLY allowed between MAJOR versions. We will maintain backward binary compatibility between PATCH and MINOR versions. For example, when we release cats 1.1.0, it will be backward binary compatible with the previous 1.0.x versions. I.E. the new JAR will be a drop-in replacement for the old one. This is critical when your application has a diamond dependency on Cats - depending on two or more libraries that all depend on Cats. If one library upgrades to the new 1.1.0 Cats before the other one does, your application still runs thanks to this backward binary compatibility.

We will also consider using organization and package name for MAJOR versioning with binary breaking changes in the future. But that decision is yet to be made.

Community

Cats is built for the FP Scala community by the FP Scala community. We can’t thank enough to our 190 (and growing) contributors and our users who provided feedbacks and suggestions.
Congratulations to all of us. Let’s celebrate this exciting milestone together.

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

Optimizing Tagless Final – Saying farewell to Free

$
0
0

The Tagless Final encoding has gained some steam recently, with some people hailing 2017 as the year of Tagless Final. Being conceptually similar to the Free Monad, different comparisons have been brought up and the one trade-off that always comes up is the lack or the difficulty of inspection of tagless final programs and in fact, I couldn’t find a single example on the web. This seems to make sense, as programs in the tagless final encoding aren’t values, like programs expressed in terms of free structures. However, in this blog post, I’d like to dispell the myth that inspecting and optimizing tagless final programs is more difficult than using Free.

Without further ado, let’s get into it, starting with our example algebra, a very simple key-value store:

trait KVStore[F[_]] {
  def get(key: String): F[Option[String]]
  def put(key: String, a: String): F[Unit]
}

To get the easiest example out of the way, here’s how to achieve parallelism in a tagless final program:

import cats._
import cats.implicits._

def program[M[_]: FlatMap, F[_]](K: KVStore[M])(implicit P: Parallel[M, F]) =
  for {
    _ <- K.put("A", a)
    x <- (K.get("B"), K.get("C")).parMapN(_ |+| _)
    _ <- K.put("X", x.getOrElse("-"))
  } yield x

This programs makes use of the cats.Parallel type class, that allows us to make use of the parMapN combinator to use independent computations with a related Applicative type. This is already much simpler than doing the same thing with Free and FreeApplicative. For more info on Parallel check out the cats docs here.

However this is kind of like cheating, we’re not really inspecting the structure of our program at all, so let’s look at an example where we actually have access to the structure to do optimizations with.

Let’s say we have the following program:

def program[F[_]: Apply](F: KVStore[F]): F[List[String]] =
    (F.get("Cats"), F.get("Dogs"), F.put("Mice", "42"), F.get("Cats"))
      .mapN((f, s, _, t) => List(f, s, t).flatten)

Not a very exciting program, but it has some definite optimization potential. Right now, if our KVStore implementation is an asynchronous one with a network boundary, our program will make 4 network requests sequentially if interpreted with the standard Apply instance of something like cats.effect.IO. We also have a duplicate request with the "Cats"-key.

So let’s look at what we could potentially do about this. The first thing we should do, is extract the static information. The easiest way to do so, is to interpret it into something we can use using a Monoid. This is essentially equivalent to the analyze function commonly found on FreeApplicative.

Getting this done, is actually quite simple, as we can use cats.Const as our Applicative data type, whenever the lefthand side of Const is a Monoid. I.e. if M has a Monoid instance, Const[M, A] has an Applicative instance. You can read more about Const here.

val analysisInterpreter: KVStore[Const[(Set[String], Map[String, String]), ?]] =
  new KVStore[Const[(Set[String], Map[String, String]), ?]] {
    def get(key: String) = Const((Set(key), Map.empty))
    def put(key: String, a: String) = Const((Set.empty, Map(key -> a)))
  }

program(analysisInterpreter).getConst
// res0: (Set[String], Map[String,String]) = (Set(Cats, Dogs),Map(Mice -> 42))

By using a Tuple of Set and Map as our Monoid, we now get all the unique keys for our get and put operations. Next, we can use this information to recreate our program in an optimized way.

def optimizedProgram[F[_]: Apply](F: KVStore[F]): F[List[String]] = {
  val (gets, puts) = program(analysisInterpreter).getConst

  puts.toList.traverse { case (k, v) => F.put(k, v) } *> gets.toList.traverse(F.get)
}

And we got our first very simple optimization. It’s not much, but we can imagine the power of this technique. For example, if we were using something like GraphQL, we could sum all of our get requests into one large request, so only one network roundtrip is made. We could imagine similar things for other use cases, e.g. if we’re querying a bunch of team members that all belong to the same team, it might make sense to just make one request to all the team’s members instead of requesting them all individually.

Other more complex optimizations could involve writing a new interpreter with the information we gained from our static analysis. One could also precompute some of the computations and then create a new interpreter with those computations in mind.

Embedding our Applicative program inside a larger monadic program is also trivial:

def program[F[_]: Apply](mouse: String)(F: KVStore[F]): F[List[String]] =
  (F.get("Cats"), F.get("Dogs"), F.put("Mice", mouse), F.get("Cats"))
    .mapN((f, s, _, t) => List(f, s, t).flatten)

def optimizedProgram[F[_]: Apply](mouse: String)(F: KVStore[F]): F[List[String]] = {
  val (gets, puts) = program(mouse)(analysisInterpreter).getConst

  puts.toList.traverse { case (k, v) => F.put(k, v) } *> gets.toList.traverse(F.get)
}

def monadicProgram[F[_]: FlatMap](F: KVStore[F]): F[Unit] = for {
  mouse <- F.get("Mice")
  list <- optimizedProgram(mouse.getOrElse("64"))(F)
  _ <- F.put("Birds", list.headOption.getOrElse("128"))
} yield ()

Here we refactor our optimizedProgram to take an extra parameter mouse. Then in our larger monadicProgram, we perform a get operation and then apply its result to optimizedProgram.

So now we have a way to optimize our one specific program, next we should see if we can introduce some abstraction. Sadly Scala lacks Rank-N types, which makes this a bit difficult as we’ll see.

First we’ll have to look at the shape of a generic program, they usually are functions from an interpreter Algebra[F] to an expression inside the type constructor F, such as F[A].

type Program[Alg[_[_]], F[_], A] = Alg[F] => F[A]

The problem of Rank-N types becomes apparent when we want to write a function where we interpret our program with two different interpreters, as we did before when interpreting into Const:

def optimize[Alg[_[_]], F[_]: Applicative, A, M: Monoid]
  (program: Alg[F] => F[A])
  (extract: Alg[Const[M, ?]])
  (restructure: M => F[A]): Alg[F] => F[A] = { interp =>

    val m = program(extract).getConst // error: type mismatch;
    // found   : extract.type (with underlying type Alg[[β$0$]cats.data.Const[M,β$0$]])
    // required: Alg[F]

    restructure(m)
  }

So, because of the lack of Rank-N types, this simple definition for our program is not enough to say that our program works for ALL type constructors F[_]: Applicative.

Fortunately there is a workaround, albeit requiring a bit more boilerplate:

trait Program[Alg[_[_]], A] {
  def apply[F[_]: Applicative](interpreter: Alg[F]) : F[A]
}

def optimize[Alg[_[_]], F[_]: Applicative, A, M: Monoid]
  (program: Program[Alg, A])
  (extract: Alg[Const[M, ?]])
  (restructure: M => F[A]): Alg[F] => F[A] = { interp =>
    val m = program(extract).getConst

    restructure(m)
  }

And now it should compile without a problem. Now we should be able to express our original optimization with this new generic approach:

def program[F[_]: Apply](mouse: String)(F: KVStore[F]): F[List[String]] =
  (F.get("Cats"), F.get("Dogs"), F.put("Mice", mouse), F.get("Cats"))
    .mapN((f, s, _, t) => List(f, s, t).flatten)

def wrappedProgram(mouse: String) = new Program[KVStore, List[String]] {
  def apply[F[_]: Applicative](alg: KVStore[F]): F[List[String]] = program(mouse)(alg)
}

def optimizedProgram[F[_]: Apply](mouse: String)(F: KVStore[F]): F[List[String]] =
  optimize(wrappedProgram)(analysisInterpreter) { case (gets, puts) =>
    puts.toList.traverse { case (k, v) => F.put(k, v) } *> gets.toList.traverse(F.get)
  }

So far so good, we’ve managed to write a function to generically optimize tagless final programs. However, one of the main advantages of tagless final is that implementation and logic should be separate concerns. With what we have right now, we’re violating the separation, by mixing the optimization part with the program logic part. Our optimization should be handled by the interpreter, just as the sequencing of individual steps of a monadic program is the job of the target Monad instance.

One way to go forward, is to create a typeclass that requires certain algebras to be optimizable. This typeclass could be written using the generic function we wrote before, so let’s see what we can come up with:

trait Optimizer[Alg[_[_]], F[_]] {
  type M

  def monoidM: Monoid[M]
  def monadF: Monad[F]

  def extract: Alg[Const[M, ?]]
  def rebuild(m: M, interpreter: Alg[F]): F[Alg[F]]

  def optimize[A](p: Program[Alg, Applicative, A]): Alg[F] => F[A] = { interpreter =>
    implicit val M: Monoid[M] = monoidM
    implicit val F: Monad[F] = monadF

    val m: M = p(extract).getConst

    rebuild(m, interpreter).flatMap(interp => p(interp))
  }
}

This might look a bit daunting at first, but we’ll go through it bit by bit. First we define our type class Optimizer parameterized by an algebra Alg[_[_]] and a type constructor F[_]. This means we can define different optimizations for different algebras and different target types. For example, we might want a different optimization for a production Optimizer[KVStore, EitherT[Task, E, ?]] and a testing Optimizer[KVStore, Id]. Next, for our interpreter we need a Monoid M for our static analysis, however we don’t to parameterize our Optimizer with an extra type parameter, since the actual type of M isn’t necessary for the API, so we use an abstract type member instead.

Next we need actual Monoid and Monad instances for F[_] and M respectively. The other two functions should seem familiar, the extract function defines an interpreter to get an M out of our program. The rebuild function takes that value of M and the interpreter and produces an F[Alg[F]], which can be understood as an F of an interpreter. This means that we can statically analyze a program and then use the result of that to create a new optimized interpreter and this is exactly what the optimize function does. This is also why we needed the Monad constraint on F, we could also get away with returning just a new interpreter Alg[F] from the rebuild method and get away with an Applicative constraint, but we can do more different things this way.

We’ll also define some quick syntax sugar for this type class to make using it a tiny bit more ergonomic.

implicit class OptimizerOps[Alg[_[_]], A](val value: Program[Alg, A]) extends AnyVal {
  def optimize[F[_]: Monad](interp: Alg[F])(implicit O: Optimizer[Alg, F]): F[A] =
    O.optimize(value)(interp)
}

Let’s see what our program would look like with this new functionality:

def monadicProgram[F[_]: Monad](F: KVStore[F])(implicit O: Optimizer[KVStore, F]): F[Unit] = for {
  mouse <- F.get("Mice")
  list <- program(mouse.getOrElse("64")).optimize(F)
  _ <- F.put("Birds", list.headOption.getOrElse("128"))
} yield ()

Looking good so far, now all we need to run this is an actual instance of Optimizer. We’ll use a Monix Task for this and for simplicity our new optimization will only look at the get operations:

implicit val kvStoreTaskOptimizer: Optimizer[KVStore, Task] = new Optimizer[KVStore, Task] {
  type M = Set[String]

  def monoidM = implicitly

  def monadF = implicitly

  def extract = new KVStore[Const[Set[String], ?]] {
    def get(key: String) = Const(Set(key))
    def put(key: String, a: String): Const[Set[String], Unit] = Const(Set.empty)
  }

  def rebuild(gs: Set[String], interp: KVStore[Task]): Task[KVStore[Task]] =
    gs.toList
      .parTraverse(key => interp.get(key).map(_.map(s => (key, s))))
      .map(_.collect { case Some(v) => v }.toMap)
      .map { m =>
        new KVStore[Task] {
          override def get(key: String) = m.get(key) match {
            case Some(a) => Option(a).pure[Task]
            case None => interp.get(key)
          }

          def put(key: String, a: String): Task[Unit] = interp.put(key, a)
        }
      }

}

Our Monoid type is just a simple Set[String] here, as the extract function will only extract the get operations inside the Set. Then with the rebuild we build up our new interpreter. First we want to precompute all the values of the program. To do so, we just run all the operations in parallel and put them into a Map, while discarding values where the get operation returned None. Now when we have that precomputed Map, we’ll create a new interpreter with it, that will check if the key given to get operation is in the precomputed Map instead of performing an actual request. We can then lift the value into a Task[Option[String]]. For all the put operations, we’ll simply run the interpreter.

Now we should have a great optimizer for KVStore programs interpreted into a Task. Let’s see how we did by interpreting into a silly implementation that only prints whenever you use one of the operations:

object TestInterpreter extends KVStore[Task] {
  def get(key: String): Task[Option[String]] = Task {

    println("Hit network for " + key)

    Option(key + "!")
  }

  def put(key: String, a: String): Task[Unit] = Task {
    println("Put something: " + a)

    ()
  }
}

Now let’s run our program with this interpreter and the optimizations!

monadicProgram(TestInterpreter).runAsync
// Hit network for Mice
// Hit network for Cats
// Hit network for Dogs
// Put something: Mice!
// Put something: Cats!

And it works, we’ve now got a principled way to write programs that can then be potentially optimized.

Conclusion

Designing a way to completely separate the problem description from the actual problem solution is fairly difficult. The tagless final encoding allows us one such fairly simple way. Using the technique described in this blog post, we should be able to have even more control over the problem solution by inspecting the structure of our program statically. We’ve seen a few roadblocks along the way, such as the lack of Rank-N types in Scala, but we might be able to come up with a macro for that in the future, making it even more ergonomic. Another thing we haven’t covered here, are programs with multiple algebras, which is quite a bit more complex as you can surely imagine, maybe that will be the topic of a follow up blog post.

The code is published right here, but might still change after getting a feeling for which API feels best.

What kind of problems and techniques would you like to see with regards to tagless final? Would love to hear from you in the comments!

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

Rethinking MonadError

$
0
0

MonadError is a very old type class, hackage shows me it was originally added in 2001, long before I had ever begun doing functional programming, just check the hackage page. In this blog post I’d like to rethink the way we use MonadError today. It’s usually used to signal that a type might be capable of error handling and is basically like a type class encoding of Eithers ability to short circuit. That makes it pretty useful for building computations from sequences of values that may fail and then halt the computation or to catch those errors in order to resume the computation. It’s also parametrized by its error type, making it one of the most common example of multi-parameter type classes. Some very common instances include Either and IO, but there are a ton more.

We can divide instances into 3 loosely defined groups:

First we have simple data types like Either, Option or Ior (with Validated not having a Monad instance).

Secondly we’ve got the IO-like types, the various IOs, Tasks and the like. These are used to suspend side effects which might have errors and therefore need to be able to handle these.

Thirdly and least importantly, we have monad transformers, which get their instances from their respective underlying monads. Since they basically just propagate their underlying instances we’re only going to talk about the first two groups for now.

The simple data types all define MonadError instances, but I wager they’re not actually used as much. This is because MonadError doesn’t actually allow us to deconstruct e.g. an Either to actually handle the errors. We’ll see more on that later, next let’s look at the IO-like types and their instances.

cats.effect.IO currently defines a MonadError[IO, Throwable], meaning that it’s fully able to raise and catch errors that might be thrown during evaluation of encapsulated side effects. Using MonadError with these effect types seems a lot more sensical at first, as you can’t escape IO even when you handle errors, so it looks like it makes sense to stay within IO due to the side effect capture.

The problem I see with MonadError is that it does not address the fundamental difference between these two types of instances. I can pattern match an Option[A] with a default value to get back an A. With IO that is just not possible. So these two groups of types are pretty different, when does it actually make sense to abstract over both of them? Well, it turns out there a few instances where it might be useful, but as we’ll see later, I’m proposing something that will be equally useful to both groups.

Now before we continue, let’s look at the MonadError type class in a bit more detail. MonadError currently comprises two parts, throwing and catching errors. To begin let’s have a look at the throw part, sometimes also called MonadThrow:

trait MonadError[F[_], E] extends Monad[F] {
  def raiseError[A](e: E): F[A]

  ...
}

This looks fine for now, but one thing that strikes me is that the F type seems to “swallow” errors. If we look at F[A] we have no clue that it might actually yield an error of type E, that fact is not required to be represented at all. However, that’s not a really big issue, so now let’s look at the catch part:

trait MonadError[F[_], E] extends MonadThrow[F, E] {
  ...

  def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A]
}

Immediately I have a few questions, if the errors are handled, why does it return the exact same type? Furthermore if this is really supposed to handle errors, what happens if I have errors in the E => F[A] function? This is even more blatant in the attempt function:

trait MonadError[F[_], E] extends Monad[F] {
  def attempt[A](fa: F[A]): F[Either[E, A]]
}

Here there is no way the outer F still has any errors, so why does it have the same type? Shouldn’t we represent the fact that we handled all the errors in the type system? This means you can’t actually observe that the errors are now inside Either. That leads to this being fully legal code:

import cats.implicits._
// import cats.implicits._

Option(42).attempt.attempt.attempt.attempt
// res0: Option[Either[Unit,Either[Unit,Either[Unit,Either[Unit,Int]]]]] = Some(Right(Right(Right(Right(42)))))

Another example that demonstrates this is the fact that calling handleError, which looks like this:

def handleError[A](fa: F[A])(f: E => A): F[A]

also returns an F[A]. This method takes a pure function E => A and thus can not fail during recovery like handleErrorWith, yet it still doesn’t give us any sign that it doesn’t throw errors. For IO-like types this is somewhat excusable as something like an unexceptional IO is still very uncommon, but for simple data types like Either or Some that function should just return an A, since that’s the only thing it can be. Just like with attempt, we can infinitely chain calls to handleError, as it will never change the type.

Ideally our type system should stop us from being able to write this nonsensical code and give us a way to show anyone reading the code that we’ve already handled errors. Now I’m not saying that the functions on MonadError aren’t useful, but only that they could be more constrained and thus more accurate in their representation.

For this purpose let’s try to write a different MonadError type class, one that’s designed to leverage the type system to show when values are error-free, we’ll call it MonadBlunder for now.

To mitigate the problems with MonadError we have a few options, the first one I’d like to present is using two different type constructors to represent types that might fail and types that are guaranteed not to. So instead of only a single type constructor our MonadBlunder class will have two:

trait MonadBlunder[F[_], G[_], E]

Our type class now has the shape (* -> *) -> (* -> *) -> * -> *, which is quite a handful, but I believe we can justify its usefulness. The first type parameter F[_] will represent our error-handling type, which will be able to yield values of type E. The second type parameter G[_] will represent a corresponding type that does not allow any errors and can therefore guarantee that computations of the form G[A] will always yield a value of type A.

Now that we figured out the shape, let’s see what we can actually do with it. For throwing errors, we’ll create a raiseError function that should return a value inside F, as it will obviously be able to yield an error.

trait MonadBlunder[F[_], G[_], E] {
  def raiseError[A](e: E): F[A]
}

This definition looks identical to the one defined one MonadError so let’s move on to error-handling. For handled errors, we want to return a value inside G, so our handleErrorWith function should indeed return a G[A]:

trait MonadBlunder[F[_], G[_], E] {
  ...

  def handleErrorWith[A](fa: F[A])(f: E => F[A]): G[A]
}

Looks good so far, right? Well, we still have the problem that f might return an erronous value, so if we want to guarantee that the result won’t have any errors, we’ll have to change that to G[A] as well:

trait MonadBlunder[F[_], G[_], E] {
  ...

  def handleErrorWith[A](fa: F[A])(f: E => G[A]): G[A]
}

And now we’re off to a pretty good start, we fixed one short coming of MonadError with this approach.

Another approach, maybe more obvious to some, might be to require the type constructor to take two arguments, one for the value and one for the error type. Let’s see if we can define raiseError on top of it:

trait MonadBlunder[F[_, _]] {
  def raiseError[E, A](e: E): F[E, A]

  ...
}

This looks pretty similar to what we already have, though now we have the guarantee that our type doesn’t actually “hide” the error-type somewhere. Next up is handleErrorWith. Ideally after we handled the error we should again get back a type that signals that it doesn’t have any errors. We can do exactly that by choosing an unhabited type like Nothing as our error-type:

trait MonadBlunder[F[_, _]] {
  ...

  def handleErrorWith[E, A](fa: F[E, A])(f: E => F[Nothing, A]): F[Nothing, A]
}

And this approach works as well, however now we’ve forced the two type parameter shape onto implementors. This MonadBlunder has the following kind (* -> * -> *) -> *. This means we can very easily define instances for types with two type parameters like Either. However, one issue might be that it’s much easier to fit a type with two type parameters onto a type class that expects a single type constructor (* -> *) than to do it the other way around.

For example try to implement the above MonadBlunder[F[_, _]] for the standard cats.effect.IO. It’s not going to be simple, whereas with the first encoding we can easily encode both Either and IO. For this reason, I will continue this article with the first encoding using the two different type constructors.

Next we’re going to look at laws we can define to make sense of the behaviour we want. The first two laws should be fairly obvious. If we flatMap over a value created by raiseError it shouldn’t propogate:

def raiseErrorStops(e: E, f: A => F[A]): Boolean =
  F.raiseError[A](e).flatMap(f) === F.raiseError[A](e)

Next we’re going to formulate a law that states, that raising an error and then immediatly handling it with a given function should be equivalent to just calling that function on the error value:

def raiseErrorHandleErrorWith(e: E, f: E => G[A]): Boolean =
  raiseError[A](e).handleErrorWith(f) === f(e)

Another law could state that handling errors for a pure value lifted into the F context does nothing and is equal to the pure value in the G context:

def handleErrorPureIsPure(a: A, f: E => G[A]): Boolean =
  a.pure[F].handleErrorWith(f) === a.pure[G]

Those should be good for now, but we’ll be able to find more when we add more derived functions to our type class. Also note that none of the laws are set in stone, these are just the ones I came up with for now, it’s completely possible that we’ll need to revise these in the future.

Now let’s focus on adding extra functions to our type class. MonadError offer us a bunch of derived methods that can be really useful. For most of those however we need access to methods like flatMap for both F and G, so before we figure out derived combinators, let’s revisit how exactly we define the type class.

The easiest would be to give both F and G a Monad constraint and move on. But then we’d have two type classes that both define a raiseError function extends Monad, and we wouldn’t be able to use them together, since that would cause ambiguities and as I’ve said before, the functions on MonadError are useful in some cases.

Instead, since I don’t really like duplication and the fact that we’re not going to deprecate MonadError overnight, I decided to extend MonadBlunder from MonadError for the F type, to get access to the raiseError function. If raiseError and handleErrorWith were instead separated into separate type classes (as is currently the case in the PureScript prelude), we could extend only the raiseError part. This also allows us to define laws that our counterparts of functions like attempt and ensure are consistent with the ones defined on MonadError. So the type signature now looks like this (expressed in Haskell, since it’s easier on the eyes):

class (MonadError f e, Monad g) => MonadBlunder f g e | f -> e, f -> g where
  ...

In Scala, we can’t express this as nicely, so we’re going to have to use something close to the cats-mtl encoding:

trait MonadBlunder[F[_], G[_], E] {
  val monadErrorF: MonadError[F, E]
  val monadG: Monad[G]
  
  ...
}

Now since this means that any instance of MonadBlunder will also have an instance of MonadError on F, we might want to rename the functions we’ve got so far. Here’s a complete definition of what we’ve come up with with raiseError removed and handleErrorWith renamed to handleBlunderWith:

trait MonadBlunder[F[_], G[_], E] {
  val monadErrorF: MonadError[F, E]
  val monadG: Monad[G]

  def handleBlunderWith[A](fa: F[A])(f: E => G[A]): G[A]
}

Now let us go back to defining more derived functions for MonadBlunder. The easiest probably being handleError, so let’s see if we can come up with a good alternative:

trait MonadBlunder[F[_], G[_], E] {
  ...

  def handleBlunder[A](fa: F[A])(f: E => A): G[A] = 
    handleBlunderWith(fa)(f andThen (_.pure[G]))
}

This one is almost exactly like handleBlunderWith, but takes a function from E to A instead of to G[A]. We can easily reuse handleBlunderWith by using pure to go back to E => G[A].

Next another function that’s really useful is attempt. Our alternative, let’s call it endeavor for now, should return a value in G instead, which doesn’t have a MonadError instance and therefore can not make any additional calls to endeavor:

trait MonadBlunder[F[_], G[_], E] {
  ...

  def endeavor[A](fa: F[A]): G[Either[E, A]] =
    handleBlunder(fa.map(Right(_)))(Left(_))
}

The implementation is fairly straightforward as well, we just handle all the errors by lifting them into the left side of an Either and map successful values to the right side of Either.

Next, let’s look at the dual to attempt, called rethrow in Cats. For MonadError it turns an F[Either[E, A]] back into an F, but we’re going to use our unexceptional type again:

trait MonadBlunder[F[_], G[_], E] {
  ...

  def absolve[A](fa: G[Either[E, A]]): F[A] = ???
}

But looking at this signature, we quickly realize that we need a way to get back to F[A] from G[A]. So we’re going to add another function to our minimal definition:

trait MonadBlunder[F[_], G[_], E] {
  val monadErrorF: MonadError[F, E]
  val monadG: Monad[G]

  def handleBlunderWith[A](fa: F[A])(f: E => G[A]): G[A]

  def accept[A](ga: G[A]): F[A]

}

This function accept, allows us to lift any value without errors into a context where errors might be present.

We can now formulate a law that values in G never stop propagating, so flatMap should always work, we do this by specifying that calling handleBlunder after calling accept on any G[A], is never going to actually change the value:

def gNeverHasErrors(ga: G[A], f: E => A): Boolean =
  accept(ga).handleBlunder(f) === ga

Now we can go back to implementing the absolve function:

def absolve[A](gea: G[Either[E, A]]): F[A] =
  accept(gea).flatMap(_.fold(raiseError[A], _.pure[F]))

Now that we’ve got the equivalent of both attempt and rethrow, let’s add a law that states that the two should cancel each other out:

def endeavorAbsolve(fa: F[A]): Boolean =
  absolve(fa.endeavor) === fa

We can also add laws so that handleBlunder and endeavor are consistent with their counterparts now that we have accept:

def deriveHandleError(fa: F[A], f: E => A): Boolean =
  accept(fa.handleBlunder(f)) === fa.handleError(f)

def deriveAttempt(fa: F[A]): Boolean =
  accept(fa.endeavor) === fa.attempt

One nice thing about attempt, is that it’s really easy to add a derivative combinator that doesn’t go to F[Either[E, A]], but to the isomorphic monad transformer EitherT[F, E, A]. We can do the exact same thing with endeavor:

def endeavorT[A](fa: F[A]): EitherT[G, E, A] =
  EitherT(endeavor(fa))

One last combinator I’d like to “port” from MonadError is the ensureOr function. ensureOr turns a successful value into an error if it does not satisfy a given predicate. We’re going to name the counterpart assureOr:

def assureOr[A](ga: G[A])(error: A => E)(predicate: A => Boolean): F[A] =
  accept(ga).flatMap(a =>
    if (predicate(a)) a.pure[F] else raiseError(error(a))
  )

This plays nicely with the rest of our combinators and we can again add a law that dictates it must be consistent with ensureOr:

def deriveEnsureOr(ga: G[A])(error: A => E)(predicate: A => Boolean): Boolean =
  ensureOr(accept(ga))(error)(predicate) === assureOr(ga)(error)(predicate)

Now we have a great base to work with laws that should guarantee principled and sensible behaviour. Next we’ll actually start defining some instances for our type class.

The easiest definitions are for Either and Option, though I’m not going to cover both, as the instances for Option can simply be derived by Either[Unit, A]and I’m going to link to the code at the end. For Either[E, A], when we handle all errors of type E, all we end up with is A, so the corresponding G type for our instance should be Id. That leaves us with the following definition:

implicit def monadBlunderEither[E]: MonadBlunder[Either[E, ?], Id, E] =
  new MonadBlunder[Either[E, ?], Id, E] {
    val monadErrorF = MonadError[Either[E, ?], E]
    val monadG = Monad[Id]

    def handleBlunderWith[A](fa: Either[E, A])(f: E => A): A = fa match {
      case Left(e) => f(e)
      case Right(a) => a
    }

    def accept[A](ga: A): Either[E, A] = Right(ga)
  }

Fairly straightforward, as Id[A] is just A, but with this instance we can already see a small part of the power we gain over MonadError. When we handle errors with handleBlunder, we’re no longer “stuck” inside the Either Monad, but instead have a guarantee that our value is free of errors. Sometimes it’ll make sense to stay inside Either, but we can easily get back into Either, so we have full control over what we want to do.

Next up, we’ll look at IO and the type that inspired this whole blog post UIO. UIO is equivalent to an IO type where all errors are handled and is short for “unexceptional IO”. UIO currently lives inside my own cats-uio library, but if things go well, we might see it inside cats-effect eventually. This would also work for IO types who use two type parameters IO[E, A] where the first represents the error type and the second the actual value. There you’d choose IO[E, A] as the F type and IO[Nothing, A] as the G type. IO[Nothing, A] there is equivalent to UIO[A].

As one might expect, you can not simply go from IO[A] to UIO[A], but we’ll need to go from IO[A] to UIO[Either[E, A]] instead, which if you look at it, is exactly the definition of endeavor. Now let’s have a look at how the MonadBlunder instance for IO and UIO looks:

implicit val monadBlunderIO: MonadBlunder[IO, UIO, Throwable] = 
  new MonadBlunder[IO, UIO, Throwable] {
    val monadErrorF = MonadError[IO, Throwable]
    val monadG = Monad[UIO]

    def handleBlunderWith[A](fa: IO[A])(f: Throwable => UIO[A]): UIO[A] =
      UIO.unsafeFromIO(fa.handleErrorWith(f andThen accept))

    def accept[A](ga: UIO[A]): IO[A] = UIO.runUIO(ga)
  }

And voila! We’ve got a fully working implementation that will allow us to switch between these two types whenever we have a guarantee that all errors are handled. This makes a lot of things much simpler. For example, if one wants to use bracket with UIO, you just need to flatMap to the finalizer, as flatMap is always guaranteed to not short-circuit.

We can also define instances for EitherT and OptionT (being isomorphic to EitherT[F, Unit, A]), where the corresponding unexceptional type is just the outer F, so endeavor is just a call to .value:

implicit def catsEndeavorForEitherT[F[_]: Monad, E]: MonadBlunder[EitherT[F, E, ?], F, E] =
  new MonadBlunder[EitherT[F, E, ?], F, E] {
    val monadErrorF = MonadError[EitherT[F, E, ?], E]
    val monadG = Monad[F]

    override def endeavor[A](fa: EitherT[F, E, A]): F[Either[E, A]] =
      fa.value

    def handleBlunderWith[A](fa: EitherT[F, E, A])(f: E => F[A]): F[A] =
      fa.value.flatMap {
        case Left(e) => f(e)
        case Right(a) => a.pure[F]
      }

    def accept[A](ga: F[A]): EitherT[F, E, A] =
      EitherT.liftF(ga)

  }

Finally, it’s also possible to create instances for other standard monad transformers like WriterT, ReaderT or StateT as long as their underlying monads themselves have instances for MonadBlunder, as is typical in mtl. As their implementations are very similar we’ll only show the StateT transformer instance:

implicit def catsEndeavorForStateT[F[_], G[_], S, E]
  (implicit M: MonadBlunder[F, G, E]): MonadBlunder[StateT[F, S, ?], StateT[G, S, ?], E] =
    new MonadBlunder[StateT[F, S, ?], StateT[G, S, ?], E] {
      implicit val F: MonadError[F, E] = M.monadErrorF
      implicit val G: Monad[G] = M.monadG

      val monadErrorF = MonadError[StateT[F, S, ?], E]
      val monadG = Monad[StateT[G, S, ?]]

      def accept[A](ga: StateT[G, S, A]): StateT[F, S, A] = ga.mapK(new (G ~> F) {
        def apply[T](ga: G[T]): F[T] = M.accept(ga)
      })

      def handleBlunderWith[A](fa: StateT[F, S, A])(f: E => StateT[G, S, A]): StateT[G, S, A] =
        IndexedStateT(s => M.handleBlunderWith(fa.run(s))(e => f(e).run(s)))

    }

In practice this means we can call handleBlunderWith on things like StateT[IO, S, A] and get back a StateT[UIO, S, A]. Pretty neat! You can also create instances for pretty much any MonadError using Unexceptional, e.g.: MonadBlunder[Future, Unexceptional[Future, ?], Throwable]. The Unexceptional type is designed to turn any erroring type into one that doesn’t throw errors by catching them with attempt.

Conclusion

In this article, I’ve tried to present the argument that MonadError is insufficient for principled error handling. We also tried to build a solution that deals with the shortcomings described earlier. Thereby it seeks not to replace, but to expand on MonadError to get a great variety of error handling capabilities. I believe the MonadBlunder type class, or whatever it will be renamed to, can be a great addition not just to the Cats community, but to the functional community at large, especially as it’s much easier to express in languages like PureScript and Haskell.

For now, all of the code lives inside the cats-uio repo, which houses the MonadBlunder type class the UIO data type and the Unexceptional data type. I hope that this blog post gave a motivation as to why I created the library and why it might be nice to adopt some of its features into the core typelevel libraries.

Note again, that none of this is final or set in stone and before it arrives anywhere might still change a lot, especially in regards to naming (which I’m not really happy with at the moment), so if you have any feedback of any sorts, please do chime in! Would love to hear your thoughts and thank you for reading this far!

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

Product with Serializable

$
0
0

A somewhat common Scala idiom is to make an abstract type extend Product with Serializable. There isn’t an obvious reason to do this, and people have asked me a number of times why I’ve done this. While I don’t think that Product or Serializable are particularly good abstractions, there’s a reason that I extend them.

Let’s say that I’m writing a simple enum-like Status type:

object EnumExample1 {
  sealed abstract class Status
  case object Pending extends Status
  case object InProgress extends Status
  case object Finished extends Status
}

Now let’s create a Set of statuses that represent incomplete items:

import EnumExample1._
val incomplete = Set(Pending, InProgress)
// incomplete: scala.collection.immutable.Set[Product with Serializable with EnumExample1.Status] = Set(Pending, InProgress)

Here, I didn’t give in explicit return type to incomplete and you may have noticed that the compiler inferred a somewhat bizarre one: Set[Product with Serializable with Status]. Why is that?

The compiler generally tries to infer the most specific type possible. Usually this makes sense. If you write val x = 3 you probably don’t want it to infer val x: Any = 3. And in the example above, I didn’t want the return type for incomplete to be inferred as Any or even Set[Any]. However, the compiler was a bit too clever and realized that not only is every item in the set an instance of Status, they are also instances of Product and Serializable since every case object (and case class) automatically extends Product and Serializable. Therefore, when it calculates the least upper bound (LUB) of the types in the set, it comes up with Product with Serializable with Status.

While there’s nothing inherently wrong with the return type of Product with Serializable with Status, it is verbose, it wasn’t what I intended, and in certain situations it might cause inference issues. Luckily there’s a simple workaround to get the inferred type that I want:

object EnumExample2 {
  // note the `extends` addition here
  sealed abstract class Status extends Product with Serializable
  case object Pending extends Status
  case object InProgress extends Status
  case object Finished extends Status
}
import EnumExample2._
val incomplete = Set(Pending, InProgress)
// incomplete: scala.collection.immutable.Set[EnumExample2.Status] = Set(Pending, InProgress)

Now since Status itself already includes Product and Serializable, Status is the LUB type of Pending, InProgress, and Finished.

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

Tagless Final algebras and Streaming

$
0
0

There have been a couple of really nice blog posts about Tagless Final and some related topics. However, I have faced some design problems when writing some algebras and haven’t seen anybody talking about. So please let me introduce this problem to you.

Algebra definition

Given the following data definition:

case class ItemName(value: String) extends AnyVal
case class Item(name: ItemName, price: BigDecimal)

Consider the following algebra:

trait ItemRepository[F[_]] {
  def findAll: F[List[Item]]
  def find(name: ItemName): F[Option[Item]]
  def save(item: Item): F[Unit]
  def remove(name: ItemName): F[Unit]
}

Let’s go through each method’s definition:

  • findAll needs to return many Items, obtainable inside a context: F[List[Item]].
  • find might or might not return an Item inside a context: F[Option[Item]].
  • save and remove will perform some actions without returning any actual value: F[Unit].

Everything is clear and you might have seen this kind of pattern before, so let’s create an interpreter for it:

import doobie.implicits._
import doobie.util.transactor.Transactor
import cats.effect.Sync

// Doobie implementation (not fully implemented, what matters here are the types).
class PostgreSQLItemRepository[F[_]](xa: Transactor[F])
                                    (implicit F: Sync[F]) extends ItemRepository[F] {

  override def findAll: F[List[Item]] = sql"select name, price from items"
                                           .query[Item]
                                           .to[List]
                                           .transact(xa)

  override def find(name: ItemName): F[Option[Item]] = F.pure(None)
  override def save(item: Item): F[Unit] = F.unit
  override def remove(name: ItemName): F[Unit] = F.unit
}

Here we are using Doobie, defined as A principled JDBC layer for Scala and one of the most popular DB libraries in the Typelevel ecosystem. And it comes with one super powerful feature: it supports Streaming results, since it’s built on top of fs2.

Now it could be very common to have a huge amount of Items in our DB that a List will not fit into memory and / or it will be a very expensive operation. So we might want to stream the results of findAll instead of have them all in memory on a List, making Doobie a great candidate for the job. But wait… We have a problem now. Our ItemRepository algebra has fixed the definition of findAll as F[List[Item]] so we won’t be able to create an interpreter that returns a streaming result instead.

Rethinking our algebra

We should think about abstracting over that List and two of the most common abstractions that immediately come to mind are Foldable and Traverse. But although these typeclasses are very useful, they are not enough to represent a stream of values, so we should come up with a better abstraction.

Well, it seems that our options are either adding another higher-kinded parameter G[_] to our algebra or just define an abstract member G[_]. So let’s go with the first one:

trait ItemRepository[F[_], G[_]] {
  def findAll: G[Item]
  def find(name: ItemName): F[Option[Item]]
  def save(item: Item): F[Unit]
  def remove(name: ItemName): F[Unit]
}

Great! This looks good so far.

Streaming support interpreter

Now let’s write a new PostgreSQL interpreter with streaming support:

import doobie.implicits._
import doobie.util.transactor.Transactor
import fs2.Stream

// Doobie implementation (not fully implemented, what matters here are the types).
class StreamingItemRepository[F[_]](xa: Transactor[F])
                                   (implicit F: Sync[F]) extends ItemRepository[F, Stream[F, ?]] {

  override def findAll: Stream[F, Item] = sql"select name, price from items"
                                           .query[Item]
                                           .stream
                                           .transact(xa)

  override def find(name: ItemName): F[Option[Item]] = F.pure(None)
  override def save(item: Item): F[Unit] = F.delay(println(s"Saving item: $item"))
  override def remove(name: ItemName): F[Unit] = F.delay(println(s"Removing item: $item"))
}

Voilà! We got our streaming implementation of findAll.

Test interpreter

That’s all we wanted, but what about testing it? Sure, we might prefer to have a simple implementation by just using a plain List, so what can we possibly do?

object MemRepository extends ItemRepository[Id, List] {
  private val mem = MutableMap.empty[String, Item]

  override def findAll: List[Item] = mem.headOption.map(_._2).toList
  override def find(name: ItemName): Id[Option[Item]] = mem.get(name.value)
  override def save(item: Item): Id[Unit] = mem.update(item.name.value, item)
  override def remove(name: ItemName): Id[Unit] = {
    mem.remove(name.value)
    ()
  }
}

That’s pretty much it! We managed to abstract over the return type of findAll by just adding an extra parameter to our algebra.

About composition

At this point the avid reader might have thought, what if I want to write a generic function that takes all the items (using findAll), applies some discounts and writes them back to the DB (using save)?

Short answer is, you might want to define a different algebra where findAll and save have the same types (eg: both of them are streams) but in case you find yourself wanting to make this work with the current types then let’s try and find out!

class DiscountProcessor[F[_], G[_]: Functor](repo: ItemRepository[F, G], join: G[F[Unit]] => F[Unit]) {

  def process(discount: Double): F[Unit] = {
    val items: G[Item] = repo.findAll.map(item => item.copy(price = item.price * (1 - discount)))
    val saved: G[F[Unit]] = items.map(repo.save)
    join(saved)
  }
}

We defined a join function responsible for evaluating the effects and flatten the result to F[Unit]. As you can see below, this works for both a streaming interpreter and a list interpreter (shout out to fthomas for proposing this solution):

object StreamingDiscountInterpreter {

  private val join: Stream[IO, IO[Unit]] => IO[Unit] = _.evalMap(identity).compile.drain

  def apply(repo: ItemRepository[IO, Stream[IO, ?]]): DiscountProcessor[IO, Stream[IO, ?]] =
    new DiscountProcessor[IO, Stream[IO, ?]](repo, join)

}

object ListDiscountInterpreter {

  private val join: List[IO[Unit]] => IO[Unit] = list => Traverse[List].sequence(list).void

  def apply(repo: ItemRepository[IO, List]): DiscountProcessor[IO, List] =
    new DiscountProcessor[IO, List](repo, join)

}

While in this case it was possible to make it generic I don’t recommend to do this at home because:

  1. it involves some extra boilerplate and the code becomes harder to understand / maintain.
  2. as soon as the logic gets more complicated you might run out of options to make it work in a generic way.
  3. you lose the ability to use the fs2 DSL which is super convenient.

What I recommend instead, is to write this kind of logic in the streaming interpreter itself. You could also write a generic program that implements the parts that can be abstracted (eg. applying a discount to an item f: Item => Item) and leave the other parts to the interpreter.

Design alternative

Another possible and very interesting alternative suggested by Michael Pilquist, would be to define our repository as follows:

trait ItemRepository[F[_], S[_[_], _]] {
  def findAll: S[F, Item]
}

Where the second type parameter matches the shape of fs2.Stream. In this case our streaming repository will remain the same (it should just extend ItemRepository[F, Stream] instead of ItemRepository[F, Stream[F, ?]]) but our in memory interpreter will now rely on fs2.Stream instead of a parametric G[_], for example:

object MemRepositoryAlt extends ItemRepository[Id, Stream] {

  override def findAll: Stream[Id, Item] = {
    sql"select name, price from items"
      .query[Item]
      .stream
      .transact(xa)
  }

}

I think it’s an alternative worth exploring further that might require a blog post on its own, so I’ll leave it here for reference :)

Source of inspiration

I’ve come up with most of the ideas presented here during my work on Fs2 Rabbit, a stream based client for Rabbit MQ, where I make heavy use of this technique as I originally described in this blog post.

Another great source of inspiration was this talk given by Luka Jacobowitz at Scale by the Bay.

Abstracting over the effect type

One thing you might have noticed in the examples above is that both ItemRepository interpreters are not fixed to IO or Task or any other effect type but rather requiring a parametric F[_] and an implicit instance of Sync[F]. This is a quite powerful technique for both library authors and application developers. Well know libraries such as Http4s, Monix and Fs2 make a heavy use of it.

And by requiring a Sync[F] instance we are just saying that our implementation will need to suspend synchronous side effects.

Once at the edge of our program, commonly the main method, we can give F[_] a concrete type. At the moment, there are two options: cats.effect.IO and monix.eval.Task. But hopefully soon we’ll have a Scalaz 8 IO implementation as well (fingers crossed).

Principle of least power

Abstracting over the effect type doesn’t only mean that we should require Sync[F], Async[F] or Effect[F]. It also means that we should only require the minimal typeclass instance that satisfies our predicate. For example:

import cats.Functor
import cats.implicits._

def bar[F[_]: Applicative]: F[Int] = 1.pure[F]

def foo[F[_]: Functor](fa: F[Int]): F[String] =
  fa.map(_.toString)

Here our bar method just returns a pure value in the F context, thus we need an Applicative[F] instance that defines pure. On the other hand, our foo method just converts the inner Int into String, what we call a pure data transformation. So all we need here is a Functor[F] instance. Another example:

import cats.Monad

def fp[F[_]: Monad]: F[String] =
`  for {
    a <- bar[F]
    b <- bar[F]
  } yield a + b

The above implementation makes use of a for-comprehension which is a syntactic sugar for flatMap and map, so all we need is a Monad[F] instance because we also need an Applicative[F] instance for bar, otherwise we could just use a FlatMap[F] instance.

Final thoughts

I think we got quite far with all these abstractions, giving us the chance to write clean and elegant code in a pure functional programming style, and there’s even more! Other topics worth mentioning that might require a blog post on their own are:

  • Dependency Injection
    • Tagless Final + implicits (MTL style) enables DI in an elegant way.
  • Algebras Composition
    • It is very common to have multiple algebras with a different F[_] implementation. In some cases, FunctionK (a.k.a. natural transformation) can be the solution.

What do you think about it? Have you come across a similar design problem? I’d love to hear your thoughts!

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

Shared State in Functional Programming

$
0
0

Newcomers to functional programming (FP) are often very confused about the proper way to share state without breaking purity and end up having a mix of pure and impure code that defeats the purpose of having pure FP code in the first place.

This is the reason that has motivated me to write a beginner friendly guide :)

Use Case

We have a program that runs three computations at the same time and updates the internal state to keep track of the tasks that have been completed. When all the tasks are completed we request the final state and print it out.

You should get an output similar to the following one:

Starting process #1
Starting process #2
Starting process #3
  ... 3 seconds
Done #2
  ... 5 seconds
Done #1
  ... 10 seconds
Done #3
List(#2, #1, #3)

We’ll use the concurrency primitive Ref[IO, List[String]] to represent our internal state because it’s a great fit.

Getting started

So this is how we might decide to start writing our code having some knowledge about cats.effect.IO:

import cats.effect._
import cats.effect.concurrent.Ref
import cats.instances.list._
import cats.syntax.all._

import scala.concurrent.duration._

object sharedstate extends IOApp {

  var myState: Ref[IO, List[String]] = _

  def putStrLn(str: String): IO[Unit] = IO(println(str))

  val process1: IO[Unit] = {
    putStrLn("Starting process #1") *>
      IO.sleep(5.seconds) *>
      myState.update(_ ++ List("#1")) *>
      putStrLn("Done #1")
  }

  val process2: IO[Unit] = {
    putStrLn("Starting process #2") *>
      IO.sleep(3.seconds) *>
      myState.update(_ ++ List("#2")) *>
      putStrLn("Done #2")
  }

  val process3: IO[Unit] = {
    putStrLn("Starting process #3") *>
      IO.sleep(10.seconds) *>
      myState.update(_ ++ List("#3")) *>
      putStrLn("Done #3")
  }

  def masterProcess: IO[Unit] = {
    myState = Ref.of[IO, List[String]](List.empty[String]).unsafeRunSync()
    val ioa = List(process1, process2, process3).parSequence.void
    ioa *> myState.get.flatMap(rs => putStrLn(rs.toString))
  }

  override def run(args: List[String]): IO[ExitCode] =
    masterProcess.as(ExitCode.Success)

}

We defined a var myState: Ref[IO, List[String]] initialized as null so we can create it on startup and all the child processes can have access to it. A so called global state.

But now we try to run our application and we encounter our first ugly problem: NullPointerException on line 19. All the processes are defined by using myState which has not yet been initialized. So an easy way to fix it is to define all our processes as lazy val.

lazy val process1: IO[Unit] = ???
lazy val process2: IO[Unit] = ???
lazy val process3: IO[Unit] = ???

That worked, brilliant! We have an application that meets the business criteria and most importantly it works!

Rethinking our application

But let’s take a step back and review our code once again, there are at least two pieces of code that should have caught your attention:

var myState: Ref[IO, List[String]] = _

We are using var and initializing our state to null, OMG! Also the workaround of lazy val should get you thinking…

And here’s the second obvious one:

myState = Ref.of[IO, List[String]](List.empty[String]).unsafeRunSync()

We require our myState to be of type Ref[IO, List[String] but the smart constructor gives us an IO[Ref[IO, List[String]]] so we are “forced” to call unsafeRunSync() to get our desired type. And there’s a reason for that, the creation of a Ref[F, A] is side-effectful, therefore it needs to be wrapped in IO to keep the purity.

But wait a minute… that unsafeRunSync() is something that you should only see at the edge of your program, most commonly in the main method that is invoked by the JVM and that is impure by nature (of type Unit). But because we are using IOApp we shouldn’t be calling any operations which names are prefixed with unsafe.

You say to yourself, yes, I know this is bad and ugly but I don’t know a better way to share the state between different computations and this works. But we know you have heard that funcional programming is beautiful so why doing this?

Functional Programming

Okay, can we do better? Of course we do and you wouldn’t believe how simple it is!

Let’s get started by getting rid of that ugly var myState initialized to null and pass it as parameter to the processes that need to access it:

import cats.effect._
import cats.effect.concurrent.Ref
import cats.instances.list._
import cats.syntax.all._

import scala.concurrent.duration._

object sharedstate extends IOApp {

  def putStrLn(str: String): IO[Unit] = IO(println(str))

  def process1(myState: Ref[IO, List[String]]): IO[Unit] = {
    putStrLn("Starting process #1") *>
      IO.sleep(5.seconds) *>
      myState.update(_ ++ List("#1")) *>
      putStrLn("Done #1")
  }

  def process2(myState: Ref[IO, List[String]]): IO[Unit] = {
    putStrLn("Starting process #2") *>
      IO.sleep(3.seconds) *>
      myState.update(_ ++ List("#2")) *>
      putStrLn("Done #2")
  }

  def process3(myState: Ref[IO, List[String]]): IO[Unit] = {
    putStrLn("Starting process #3") *>
      IO.sleep(10.seconds) *>
      myState.update(_ ++ List("#3")) *>
      putStrLn("Done #3")
  }

  def masterProcess: IO[Unit] = {
    val myState: Ref[IO, List[String]] = Ref.of[IO, List[String]](List.empty[String]).unsafeRunSync()

    val ioa = List(process1(myState), process2(myState), process3(myState)).parSequence.void
    ioa *> myState.get.flatMap(rs => putStrLn(rs.toString))
  }

  override def run(args: List[String]): IO[ExitCode] =
    masterProcess.as(ExitCode.Success)

}

Great! We got rid of that global state and we are now passing our Refas a parameter. Remember that it is a concurrency primitive meant to be accesed and modified in concurrent scenarios, so we are safe here.

Notice how all our processes are now defined as def processN(myState: Ref[IO, List[String]]).

A well known method: flatMap!

Now, we still have that unsafeRunSync() hanging around our code, how can we get rid of it? The answer is flatMap!!!

def masterProcess: IO[Unit] =
  Ref.of[IO, List[String]](List.empty[String]).flatMap { myState =>
    val ioa = List(process1(myState), process2(myState), process3(myState)).parSequence.void
    ioa *> myState.get.flatMap(rs => putStrLn(rs.toString))
  }

You only need to call flatMap once up in the call chain where you call the processes to make sure they all share the same state. If you don’t do this, a new Ref will be created every time you flatMap (remember creating a Ref is side effectful!) and thus your processes will not be sharing the same state changing the behavior of your program.

We now have a purely functional code that shares state in a simple and pure fashion. Here’s the entire FP program:

import cats.effect._
import cats.effect.concurrent.Ref
import cats.instances.list._
import cats.syntax.all._

import scala.concurrent.duration._

object sharedstate extends IOApp {

  def putStrLn(str: String): IO[Unit] = IO(println(str))

  def process1(myState: Ref[IO, List[String]]): IO[Unit] = {
    putStrLn("Starting process #1") *>
      IO.sleep(5.seconds) *>
      myState.update(_ ++ List("#1")) *>
      putStrLn("Done #1")
  }

  def process2(myState: Ref[IO, List[String]]): IO[Unit] = {
    putStrLn("Starting process #2") *>
      IO.sleep(3.seconds) *>
      myState.update(_ ++ List("#2")) *>
      putStrLn("Done #2")
  }

  def process3(myState: Ref[IO, List[String]]): IO[Unit] = {
    putStrLn("Starting process #3") *>
      IO.sleep(10.seconds) *>
      myState.update(_ ++ List("#3")) *>
      putStrLn("Done #3")
  }

  def masterProcess: IO[Unit] =
    Ref.of[IO, List[String]](List.empty[String]).flatMap { myState =>
      val ioa = List(process1(myState), process2(myState), process3(myState)).parSequence.void
      ioa *> myState.get.flatMap(rs => putStrLn(rs.toString))
    }

  override def run(args: List[String]): IO[ExitCode] =
    masterProcess.as(ExitCode.Success)

}

Mutable reference

As I mentioned in one of the sections above, the creation of Ref[F, A] is side-effectful. So what does this mean? Does it write to disk? Does it perform HTTP Requests? Not exactly.

It all comes down to wanting to keep the property of referential transparency while sharing and mutating state. So let’s again put up an example to follow up along with some explanation:

var a = 0
def set(n: Int) = a = n
def get: Int = a

Here we have imperative and impure code that mutates state. So we can try wrapping things in IO to keep side effects under control:

class IORef {
  var a: Int = 0
  def set(n: Int): IO[Unit] = IO(a = n)
  def get: IO[Int] = IO.pure(a)
}

This is way better since now the mutation is encapsulated within IORef but we are now pushing some resposibility to whoever creates an IORef. Consider this:

val ref = new IORef()

If we have two or more references to ref in our code, they will be referring to the same mutable state and we don’t really want that. We can make sure this doesn’t happen and a way to achieve this is to wrap the creation of IORef in IO:

private class IORef {
  var a: Int = 0
  def set(n: Int): IO[Unit] = IO(a = n)
  def get: IO[Int] = IO.pure(a)
}

object IORef {
  def apply: IO[IORef] = IO(new IORef)
}

We have now regained purity. So whenever you create an IORef you’ll get an IO[IORef] instead of a mutable reference to IORef. This means that when you invoke flatMap on it twice you’ll get two different mutable states, and this is the power of Referential Transparency. It gives you way more control than having a val ref hanging around in your code and gives you local reasoning.

All these examples are written in terms of IO for the sake of simplicity but in practice they are polymorphic on the effect type.

Applying the technique in other libraries

Although in the example above we only see how it’s done with the cats-effect library, this principle expands to other FP libraries as well.

For example, when writing an http4s application you might need to create an HttpClient that needs to be used by more than one of your services. So again, create it at startup and flatMap it once:

object HttpServer extends StreamApp[IO] {

  override def stream(args: List[String], requestShutdown: IO[Unit]): Stream[IO, ExitCode] =
    for {
      httpClient <- Http1Client.stream[IO]()
      endpoint1  = new HttpEndpointOne[IO](httpClient)
      endpoint2  = new HttpEndpointTwo[IO](httpClient)
      exitCode   <- BlazeBuilder[F]
                      .bindHttp(8080, "0.0.0.0")
                      .mountService(endpoint1)
                      .mountService(endpoint2)
                      .serve
    } yield exitCode

}

class HttpEndpointOne[F[_]](client: Client[F]) { ... }
class HttpEndpointTwo[F[_]](client: Client[F]) { ... }

When writing fs2 applications you can apply the same technique, for example between processes that share a Queue, Topic, Signal, Semaphore, etc.

Remember that if you are forced to call unsafeRunSync() other than in your main method it might be a code smell.

Conclusion

To conclude this post I would like to give a big shout out to @SystemFW who has been untiringly teaching this concept in the Gitter channels. And here’s a quote from his response on Reddit:

At the end of the day all the benefits from referential transparency boil down to being able to understand and build code compositionally. That is, understanding code by understanding individual parts and putting them back together, and building code by building individual parts and combining them together. This is only possible if local reasoning is guaranteed, because otherwise there will be weird interactions when you put things back together, and referential transparency is defined as something that guarantees local reasoning.

In the specific case of state sharing, this gives rise to a really nice property: since the only way to share is passing things as an argument, the regions of sharing are exactly the same of your call graph, so you transform an important aspect of the behaviour (“who shares this state?”) into a straightforward syntactical property (“what methods take this argument”?). This makes shared state in pure FP a lot easier to reason about than its side-effectful counterpart imho.

In simple terms, remind yourself about this: “flatMap once and pass the reference as an argument!”

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.


Typedapi or how to derive your clients and servers from types

$
0
0

In this blog post, I will show you how to leverage Scala’s type system to derive an HTTP client function from a single type. This will also be the story of how I started to work on Typedapi which is basically the attempt to bring Haskell’s Servant to Scala.

Servant in a nutshell and how it began

For everyone not knowing Servant, it is a library which lets you define your web apis as types and derives the client and server functions from it. When I saw it for the first time while working on a pet project I immediately loved the idea. Creating web server and clients this way reduces your code to a mere type, you get extra type safety and you can use the api types as contracts between your server and its clients.

I couldn’t find any viable alternative in Scala at the time and decided to build it on my own. But I just wanted to start with a single feature to not overwhelm myself and abandon the project after a short time. Therefore, I set out to make Scala able to derive a client function from a single api type, as we will do in this post.

Derive a client function from a type. How hard can it be?

Let’s start with an example we will use later on to ease understanding. Consider the following api:

GET /users/:name?minAge=:age -> List[User]

It only consists of a single endpoint which returns a list of Users:

final case class User(name: String, age: Int)

with a given name: String. Furthermore, you filter the resulting users by their age: Int. Our big goal is to end up with a function which is derived from a type-level representation of our endpoint:

(name: String, minAge: Int) => F[List[User]]

Represent the api as a type

Question: how do you represent the above api as a type in Scala? I think the best way is to break it apart and try to find type-level representations for each element. After that, we “just” merge them together.

When we take a closer look at our endpoint we see that it consists of:

  • a method GET to identify which kind of operation we want to do and which also describes the expected return type
  • constant path elements identifying an endpoint: /users
  • dynamic path elements called “segments” which represent input parameters with a name and type: :name
  • queries which again represent input parameters with a name and type: minAge=[age]

Or in other words, just a plain HTTP definition of a web endpoint. Now that we know what we are working with let’s try and find a type-level representation.

But how do you transform a value-level information as a type? First of all, the value has to be known at compile time which leaves us with literals. If we would work with Dotty we could leverage a concept called literal type:

type Path = "users"

But since we want to stay in Vanilla Scala this will not work. We have to take another route by using a tool probably every developer has to use when it comes to working on the type-level called shapeless. It has this nifty class Witness which comes with an abstract type T. And T is exactly what we need here as it transforms our literals into types.

import shapeless.Witness

val usersW = Witness("users")

But this isn’t a pure type declaration, you will say. And you are right, but right now there is no other way in Scala. We have to go the ordinary value road first to create our types.

Now that we know how to get a type representation from a String which describes our path we should clearly mark it as a path element:

sealed trait Path[P]

type users = Path[usersW.T]

That’s it. That is the basic concept of how we can describe our apis as types. We just reuse this concept now for the remaining elements like the segment.

val nameW = Witness('name)

sealed trait Segment[K, V]

type name = Segment[nameW.T, String]

Do you see how we included the segment’s identifier in the type? This way we are not only gain information about the expected type but also what kind of value we want to see. By the way, I decided to use Symbols as identifiers, but you could also switch to String literals. The remaining definitions look pretty similar:

val minAgeW = Witness('minAge)

sealed trait Query[K, V]

type minAge = Query[minAgeW.T, Int]

sealed trait Method
sealed trait Get[A] extends Method

Here, A in Get[A] represents the expected result type of our api endpoint.

Now that we know how to obtain the types of our api elements we have to put them together into a single type representation. After looking through shapeless’s features we will find HLists, a list structure which can store elements of different types.

import shapeless.{::, HNil}

type Api = Get[List[User]] :: users :: name :: minAge :: HNil

Here you go. Api is an exact representation of the endpoint we defined at the beginning. But you don’t want to write Witness and HLists all the time so let’s wrap it up into a convenient function call:

def api[M <: Method, P <: HList, Q <: HList, Api <: HList]
       (method: M, path: PathList[P], queries: QueryList[Q])
       (implicit prepQP: Prepend.Aux[Q, P, Api]): ApiTypeCarrier[M :: Api] = ApiTypeCarrier()
      
val Api = api(Get[List[User]], Root / "users" / Segment[String]('name), Queries.add(Query[Int]('minAge)))

Not clear what is happening? Let’s take a look at the different elements of def api(...):

  • method should be obvious. It takes some method type.
  • PathList is a type carrier with a function def /(...) to concatenate path elements and segments. In the end, PathList only stores the type of an HList and nothing more.
final case class PathList[P <: HList]() {
  
  def /[S](path: Witness.Lt[S]): PathList[S :: P] = PathList()
  ...
}

val Root = PathList[HNil]()
  • Same is true for QueryList.
  • The last step is to merge all these HLists types into a single one. Shapeless comes again with a handy type class called Prepend which provides us with the necessary functionality. Two HList types go in, a single type comes out. And again, we use a type carrier here to store the api type.

Whoho, we did it. One thing we can mark as done on our todo list. Next step is to derive an actual client function from it.

Clients from types

So far we have a type carrier describing our api as type:

ApiTypeCarrier[Get[List[User]] :: Query[minAgeW.T, Int] :: Segment[nameW.T, String] :: usersW.T :: HNil]

Now we want to transform that into a function call (name: String, minAge: Int) => F[List[User]]. So what we need is the following:

  • the types of our expected input
  • the output type
  • the path to the endpoint we want to call

All information are available but mixed up and we need to separate them. Usually, when we work with collections and want to change their shape we do a fold and alas shapeless has type classes to fold left and right over an HList. But we only have a type. How do we fold that?

Type-level FoldLeft

What we want is to go from Api <: HList to (El <: HList, KIn <: HList, VIn <: HList, M, Out) with:

  • El al the elements in our api: "users".type :: SegmentInput :: QueryInput :: GetCall :: HNil
  • KIn the input key types: nameW.T :: minAgeW.T :: HNil
  • VIn the input value types: String :: Int :: HNil
  • the method type: GetCall
  • and Out: List[User]

Here, we introduced new types SegmentInput and QueryInput which act as placeholders and indicate that our api has the following inputs. This representation will come in handy when we construct our function.

Now, how to fold on the type-level? The first step, we have to define a function which describes how to aggregate two types:

trait FoldLeftFunction[In, Agg] { type Out }

That’s it. We say what goes in and what comes out. You need some examples to get a better idea? Here you go:

implicit def pathTransformer[P, El <: HList, KIn <: HList, VIn <: HList, M, Out] = 
  FoldLeftFunction[Path[P], (El, KIn, VIn, M, Out)] { type Out = (P :: El, KIn, VIn, Out) }

We expect a Path[P] and intermediate aggregation state (El, KIn, VIn, M, Out). We merge the two by adding P to our list of api elements. The same technique is also used for more involved aggregations:

implicit def segmentTransformer[K <: Symbol, V, El <: HList, KIn <: HList, VIn <: HList, M, Out] = 
  FoldLeftFunction[Segment[K, V], (El, KIn, VIn, M, Out)] { type Out = (SegmentInput :: El, K :: KIn, V :: VIn, Out) }

Here, we get some Segment with a name K and a type V and an intermediate aggregation state we will update by adding a placeholder to El, the name to KIn and the value type to VIn.

Now that we can aggregate types we need a vehicle to traverse our HList type and transform it on the fly by using our FoldLeftFunction instances. I think yet another type class can help us here.

trait TypeLevelFoldLeft[H <: HList, Agg] { type Out }

object TypeLevelFoldLeft {

  implicit def returnCase[Agg] = new TypeLevelFoldLeft[HNil, Agg] {
    type Out = Agg
  }

  implicit def foldCase[H, T <: HList, Agg, FfOut, FOut](implicit f: FoldLeftFunction.Aux[H, Agg, FfOut], 
                                                                  next: Lazy[TypeLevelFoldLeft.Aux[T, FfOut, FOut]]) = 
    new TypeLevelFoldLeft[H :: T, Agg] { type Out = FOut }
}

The above definition describes a recursive function which will apply the FoldLeftFunction on H and the current aggregated type Agg and continues with the resulting FfOut and the remaining list. And before you bang your head against the wall for hours until the clock strikes 3 am, like I did, a small hint, make next lazy. Otherwise, Scala is not able to find next. My guess is that Scala is not able to infer next, because it depends on FfOut which is also unknown. So we have to defer next’s inference to give the compiler some time to work.

And another hint, you can start with Unit as the initial type for your aggregate.

Collect all the request data

We folded our api type into the new representation making it easier now to derive a function which collects all the data necessary to make a request.

// path to our endpoint described by Path and Segment
type Uri = List[String]

// queries described by Query
type Queries = Map[String, List[String]]

VIn => (Uri, Queries)

This function will form the basis of our client function we try to build. It generates the Uri and a Map of Queries which will be used later on to do a request using some HTTP library.

By now, you should be already comfortable with type classes. Therefore, it shouldn’t shock you that I will introduce yet another one to derive the above function.

trait RequestDataBuilder[El <: HList, KIn <: HList, VIn <: HList] {

  def apply(inputs: VIn, uri: Uri, queries: Queries): (Uri, Queries)
}

Instances of this type class update uri and queries depending on the types they see. For example, if the current head of El is a path element we prepend its String literal to uri. Just keep in mind to reverse the List before returning it.

implicit def pathBuilder[P, T <: HList, KIn <: HList, VIn <: HList](implicit wit: Witness.Aux[P], next: RequestDataBuilder[T, KIn, VIn]) = 
  new RequestDataBuilder[P :: T, KIn, VIn] {
    def apply(inputs: VIn, uri: Uri, queries: Queries): (Uri, Queries) =
      next(inputs, wit.value.toString() :: uri, queries, headers)
  }

Or if we encounter a query input we derive the key’s type-literal, pair it with the given input value and add both to queries:

implicit def queryBuilder[K <: Symbol, V, T <: HList, KIn <: HList, VIn <: HList](implicit wit: Witness.Aux[K], next: RequestDataBuilder[T, KIn, VIn]) = 
  new RequestDataBuilder[QueryInput :: T, K :: KIn, V :: VIn] {
    def apply(inputs: V :: VIn, uri: Uri, queries: Queries): (Uri, Queries) =
      next(inputs.tail, uri, Map(wit.value.name -> List(inputs.head.toString())) ++ queries)
  }

The other cases are looking quite similar and it is up to the interested reader to find the implementations.

What we end up with is a nested function call structure which will take an HList and returns the uri and queries.

val builder = implicitly[RequestDataBuilder[El, KIn, VIn]]

val f: VIn => (Uri, Queries) = input => builder(input, Nil, Map.empty)

"joe" :: 42 :: HNil => (List("users", "joe"), Map("minAge" -> List("42")))

Here, "joe" and 42 are our expected inputs (VIn) which we derived from the segments and queries of our Api.

Make the request

We have all the data we need to make an IO request but nothing to execute it. We change that now. By adding an HTTP backend. But we don’t want to expose this implementation detail through our code. What we want is a generic description of a request action and that sounds again like a job for type classes.

trait ApiRequest[M, F[_], C, Out] {

  def apply(data: (Uri, Queries), client: C): F[Out]
}

We have to specialize that for the set of methods we have:

trait GetRequest[C, F[_], Out] extends ApiRequest[GetCall, C, F, Out]

...

val request = implicitly[ApiRequest[GetCall, IO, C, List[User]]]

val f: VIn => IO[List[User]] = 
  input => request(builder(input, Nil, Map.empty), c)

Let’s say we want http4s as our backend. Then we just have to implement these traits using http4s functionality.

Make it a whole

We have a bunch of type classes which in theory do a request, but so far they are completely useless. To make a working piece of code out of it we have to connect them.

def derive[Api <: HList, El <: HList, KIn <: HList, VIn <: HList, M, Out, F[_], C]
  (api: ApiTypeCarrier[Api], client: C)
  (implicit fold: Lazy[TypeLevelFoldLeft.Aux[Api, Fold], (El, KIn, VIn, M, Out)]
            builder: RequestBuilder[El, KIn, VIn],
            request: ApiRequest[M, F, C, Out]): VIn => F[Out] = vin => request(builder.apply(vin, List.newBuilder, Map.empty), client)

The first approach gives us the desired function. It transforms our api type into a (El, KIn, VIn, Method, Out) representation, derives a function to collect all data to do a request, and finds an IO backend to actually do the request. But it has a major drawback. You have to fix F[_] somehow and the only way is to set it explicitly. But by doing that you are forced to provide definitions for all the type parameters. Furthermore, this function isn’t really convenient. To use it you have to create and pass an HList and as we said before, we don’t want to expose something like that.

To fix the first problem we simply add a helper class which moves the step of defining the higher kind F[_] to a separate function call:

final class ExecutableDerivation[El <: HList, KIn <: HList, VIn <: HList, M, O](builder: RequestDataBuilder[El, KIn, VIn], input: VIn) {

  final class Derivation[F[_]] {

    def apply[C](client: C)(implicit req: ApiRequest[M, C, F, O]): F[O] = {
      val data = builder(input, List.newBuilder, Map.empty, Map.empty)

      req(data, cm)
    }
  }

  def run[F[_]]: Derivation[F] = new Derivation[F]
}

Making a function of arity Length[VIn] out of Vin => F[O]is possible by using shapeless.ops.function.FnFromProduct.

When we apply both solutions we end up with:

def derive[H <: HList, Fold, El <: HList, KIn <: HList, VIn <: HList, M, Out]
  (apiList: ApiTypeCarrier[H])
  (implicit fold: Lazy[TypeLevelFoldLeft.Aux[H, Unit, (El, KIn, VIn, M, Out)]],
            builder: RequestDataBuilder[El, KIn, VIn],
            vinToFn: FnFromProduct[VIn => ExecutableDerivation[El, KIn, VIn, M, Out]]): vinToFn.Out = 
  vinToFn.apply(input => new ExecutableDerivation[El, KIn, VIn, M, Out](builder, input))

I already hear the “your function signature is so big …” jokes incoming, but this is basically what we will (and want to) end up with when doing type-level programming. In the end, our types have to express the logic of our program and that needs some space.

But finally, we can say we did it! We convinced the Scala compiler to derive a client function from a type. Let’s have a look at our example to see how it works.

import cats.effect.IO
import org.http4s.client.Client

val Api = api(Get[List[User]], Root / "users" / Segment[String]('name), Queries.add(Query[Int]('minAge)))
val get = derive(Api)

get("joe", 42).run[IO](Client[IO]) // IO[List[User]]

Conclusion

When you take a closer look at the code above you will see that we were able to move most of the heavy lifting to the compiler or shapeless therefore reducing our code to a relatively small set of “simple” type classes. And when literal types are in thing in Scala we can also remove most of the boilerplate necessary to create our api types.

This, again, shows me how powerful Scalas type system is and how much you can gain when you embrace it.

Next Step - Typedapi

Now that we are able to derive a single client function from a type we should also be able to do the same for a collection of api types. And if we are already on it, let’s add server-side support. Or … you just use Typedapi. It already comes with the following features:

  • client function derivation
  • server function derivation
  • single and multi api type handling
  • support for htt4s
  • support for akka-http in the making
  • simple interface to add more HTTP frameworks/libraries

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

Optimizing Tagless Final – Part 2 – Monadic programs

$
0
0

In our previous post on optimizing tagless final programs we learned how we could use the sphynx library to derive some optimization schemes for your tagless final code. In case you missed it and want to read up on it, you can find it right here or you can watch my presentation on the topic here, but you should be able to follow this blog post without going through it all in detail.

Optimizing monadic programs

One of the questions I’ve been getting a lot, is if we can also do something like that for the monadic parts of our program. The answer is yes, we can, however it will have to be quite a bit different.

I don’t think the differences are quite obvious, so we’ll go through them step by step. With applicative programs, we’re optimizing a bunch of independent instructions. That means, we can look at all of them and extract information out of them statically (i.e. without running the interpreter). They can be seen as a sequence of instructions that we can fold down to a single monoid M, that holds the information that we need to optimize. We then used that monoid to recreate a new interpreter that can take this extra information into account.

With monadic programs, we do not have such luxury. We can only step through each of our instructions one at a time, because every instruction depends on the results of the prior one. This means that we cannot extract any information beyond the very first instruction we have. That might seem like a deal breaker, but there’s still a few things we can do. We could, for example, build up our monoid M dynamically, after each monadic instruction. Then, before invoking the next computation in the monadic sequence, we could take that monoid and recreate that next computation with that extra information.

Now, that might sound super abstract to you, and I wouldn’t disagree, so let’s look at a quick example. Say, we’re using the KVStore algebra again from last time:

trait KVStore[F[_]] {
  def get(key: String): F[Option[String]]
  def put(key: String, value: String): F[Unit]
}

We could optimize programs with this algebra by caching the results of get and we could use that same cache to also cache key-value pairs we inserted using put.

So given this example program:

def program[F[_]: Monad](key: String)(F: KVStore[F]): F[List[String]] = for {
  _ <- F.put(key, "cat")
  dog <- F.get(key)
  cat <- F.get(dog.getOrElse("dog"))
  cat2 <- F.get("cat")
} yield List(dog, cat, cat2).flatten

The naively interpreted program would be doing the following things:

  1. put the value “cat” into the store with the key passed by the user
  2. get the value “cat” back out of the store
  3. access the key-value store and maybe return a value associated with the key “cat”
  4. access the store again with the same “cat” key.

Now if accessing the key-value store means going through a network layer this is of course highly inefficient. Ideally our fully optimized program should do the following things:

  1. put the value “cat” into the store with the key parameter passed by the user and cache it.
  2. access the cache to get the value “cat” associated with key
  3. access the key-value-store and maybe return a value associated with the key “cat”
  4. access the cache to return the previous result for the “cat” key.

Cool, next, let’s look at how we might get there. First the type of our cache, which for our case can just be a Map[String, String], but generically could just be any monoid.

Now what we want to do is transform any interpreter for KVStore programs into interpreters that

  1. Look in the cache before performing a get action with the actual interpreter
  2. Write to the cache after performing either a get or put action.

So how can we get there? It seems like we want to thread a bunch of state through our program, that we want to both read and write to. If you’re familiar with FP folklore you might recognize that that description fits almost exactly to the State monad. Furthermore, because we know that our F[_] is a monad, that means the StateT monad transformer over F will also be a monad.

Okay with that said, let’s try to develop function that turns any interpreter KVStore[F] into an interpreter into StateT[F, M, A], so an KVStore[StateT[F, M, ?]], where M is the monoid we use to accumulate our extracted information. We’ll start with the put operation. For put, we’ll want to call the interpreter to perform the action and then modify the state by adding the retrieved value into our cache. To make the code a bit more legible we’ll also define a few type aliases.

type Cache = Map[String, String]
type CachedAction[A] = StateT[F, Cache, A]

def transform(interp: KVStore[F]): KVStore[CachedAction] = new KVStore[CachedAction] {
  def put(key: String, v: String): CachedAction[Unit] =
    StateT.liftF[F, Cache, Unit](interp.put(key, v)) *> StateT.modify(_.updated(key, v))

  def get(key: String): CachedAction[Option[String]] = ???
}

So far, so good, now let’s have a look at what to do with the get function. It’s a bit more complex, because we want to read from the cache, as well as write to it if the cache didn’t include our key. What we have to do is, get our current state, then check if the key is included, if so, just return it, otherwise call the interpreter to perform the get action and then write that into the cache.

def get(key: String): CachedAction[Option[String]] = for {
  cache <- StateT.get[F, Cache]
  result <- cache.get(key) match {
              case s @ Some(_) => s.pure[CachedAction]
              case None => StateT.liftF[F, Cache, Option[String]](interp.get(key))
                             .flatTap(updateCache(key))
            }
} yield result

def updateCache(key: String)(ov: Option[String]): CachedAction[Unit] = ov match {
  case Some(v) => StateT.modify(_.updated(key, v))
  case None => ().pure[CachedAction]
}

This is quite something, so let’s try to walk through it step by step. First we get the cache using StateT.get, so far so good. Now, we check if the key is in the cache using cache.get(key). The result of that is an Option[String], which we can pattern match to see if it did include the key. If it did, then we can just return that Option[String] by lifting it into CachedAction using pure. If it wasn’t in the cache, things are a bit more tricky. First, we lift the interpreter action into CachedAction using StateT.liftF, that gives us a CachedAction[Option[String]], which is already the return type we need and we could return it right there, but we still need to update the cache. Because we already have the return type we need, we can use the flatTap combinator. Then inside the updateCache function, we take the result of our interpreter, which is again an Option[String], and update the cache if the value is present. If it’s empty, we don’t want to do anything at all, so we just lift unit into CachedAction.

In case you’re wondering flatTap works just like flatMap, but will then map the result type back to the original one, making it a bit similar to a monadic version of the left shark (<*) operator, making it very useful for these “fire-and-forget” operations. It’s defined like this:

def flatTap[F[_]: Monad, A, B](fa: F[A])(f: A => F[B]): F[A] =
  fa.flatMap(a => f(a).map(b => a))

And with that we now have a working function to turn any interpreter into an optimized interpreter. We can also generalize this fairly easily into a function that will do all of the wiring for us. To do so, we’ll generalize away from KVStore and Cache and instead use generic Alg[_[_]] and M parameters:

def optimize[Alg[_[_]], F[_]: Monad, M: Monoid, A]
  (program: MonadProgram[Alg, A])
  (withState: Alg[F] => Alg[StateT[F, M, ?]]): Alg[F] => F[A] = interpreter =>
    program(withState(interpreter)).runEmptyA

Just like last time, we have to use a MonadProgram wrapper around Alg[F] => F[A], because Scala lacks rank-N types which would allow us to define values that work over ALL type constructors F[_]: Monad (Fortunately however, this will very probably soon be fixed in dotty, PR here).

Now let’s see if we can actually use it, by checking it with a test interpreter that will print whenever we retrieve or insert values into the KVStore.

optimize[KVStore, IO, Cache, List[String]](program("mouse"))(transform)
  .apply(printInterpreter)
  .unsafeRunSync()

// Put key: mouse, value: cat
// Get key: cat

It works and does exactly what we want! Nice! We could end this blog post right here, but there’s still a couple of things I’d like to slightly alter.

Refining the API

As you were able to tell the implementation of our transformation from the standard interpreter to the optimized interpreter is already quite complex and that is for a very very simple algebra that doesn’t do a lot. Even then, I initially wrote an implementation that packs everything in a single StateT constructor to avoid the overhead of multiple calls to flatMap, but considered the version I showed here more easily understandable. For more involved algebras and more complex programs, all of this will become a lot more difficult to manage. In our last blog post we were able to clearly separate the extraction of our information from the rebuilding of our interpreter with that information. Let’s have a look at if we can do the same thing here.

First we’ll want to define an extraction method. For applicative programs we used Const[M, ?], however that cannot work here, as Const doesn’t have a Monad instance and also, because for extraction with monadic programs, we need to actually take the result of the computation into account. That means, that for every operation in our algebra, we want a way to turn it into our monoid M. With that said, it seems we want a function A => M, where A is the result type of the operations in our algebra. So what we can do here is define an algebra for ? => M, in types an Alg[? => M].

Let’s try to do define such an interpreter for our KVStore along with Cache/Map[String, String:

def extract: KVStore[? => Cache] = new KVStore[? => Cache] {
  def get(key: String): Option[String] => Cache = {
    case Some(s) => Map(key -> s)
    case None => Map.empty
  }

  def put(key: String, a: String): Unit => Cache =
    _ => Map(key -> a)
}

Just as before we want to extract the cache piece by piece with every monadic step. Whenever we get an Option[String] after using get, we can then turn that into a Cache if it’s non-empty. The same goes for put, where we’ll create a Map using the key-value pair. We now have a way to turn the results of our algebra operations into our information M, so far so good!

Next, we’ll need a way to rebuild our operations using that extracted information. For that, let’s consider what that actually means. For applicative programs this meant a function that given a state M and an interpreter Alg[F], gave a reconstructed interpreter inside the F context F[Alg[F]]. So a function (M, Alg[F]) => F[Alg[F]].

For monadic programs, there’s no need to precompute any values, as we’re dealing with fully sequential computations that can potentially update the state after every evaluation. So we’re left with a function (M, Alg[F]) => Alg[F]. Let’s try building that for KVStore:

def rebuild(m: M, interp: KVStore[F]): KVStore[F] = new KVStore[F] {
  def get(key: String): F[Option[String]] = m.get(key) match {
    case o @ Some(_) => Monad[F].pure(o)
    case None => interp.get(key)
  }

  def put(key: String, a: String): F[Unit] =
    m => interp.put(key, a)
}

Easy enough! For get we look inside our cache and use the value if it’s there, otherwise we call the original interpreter to do its job. For put, there’s nothing to gain from having access to our extracted information and the only thing we can do is call the interpreter and let it do what needs to be done.

Now we have a way to extract information and then also use that information, next up is finding a way to wire these two things together to get back to the behaviour we got using StateT.

And as a matter of fact, we’ll wire them back together using exactly StateT, as it’s monad instance does do exactly what we want.

Using our two functions extract and rebuild it’s fairly easy to get back to KVStore[StateT[F, Cache, ?]]:

def transform(interp: KVStore[F]): KVStore[StateT[F, Cache, ?]] = new KVStore[StateT[F, Cache, ?]] {
  def put(key: String, v: String): StateT[F, Cache, Unit] =
    StateT(cache => rebuild(cache, interp).put(key, v).map(a => 
      (cache |+| extract.put(key, v)) -> a))

  def get(key: String): StateT[F, Cache, Option[String]] =
    StateT(cache => rebuild(cache, interp).get(key).map(a => 
      (cache |+| extract.get(key)) -> a))
}

This is fairly straightforward, we use rebuild with our cache and the interpreter to get a new interpreter that will run the operation. Then, we use the result, which is just an F[Unit]/F[Option[String]] respectively, and map it using the extractor to get the newest Cache and using its Monoid instance to update the state and then we tuple it with the result, giving us an F[(Cache, Unit)] or F[(Cache, Option[String])], which is exactly what the StateT constructor needs.

This is great, but can we generalize this to any algebra and any monoid?

The answer is yes, but it’s not exactly easy. First let’s look at the actual problem. We have two interpreters extract and rebuild, but we have no way to combine them, because Alg, is completely unconstrained and that means we can’t call any functions on a generic Alg[F] at all. So, okay, we need to constrain our Alg parameter to be able to combine values of Alg[F] with values of Alg[G] in some way, but what kind of type class could that be? Are there even type classes that operate on the kind of Alg?

Higher kinded things

There are, they’re just hidden away in a small library called Mainecoon. That library gives us higher kinded versions of things like functors and contravariant functors, called FunctorK and ContravariantK respectively.

Let’s have a quick look at FunctorK:

@typeclass
trait FunctorK[A[_[_]]] {
  def mapK[F[_], G[_]](af: A[F])(f: F ~> G): A[G]
}

Instead of mapping over type constructors F[_], we map over algebras A[_[_]] and insteading of using functions A => B, we use natural transformations F ~> G. This is nice, but doesn’t really get us that far.

What we really need is the equivalent of the Applicative/Apply map2 operation. map2 looks like this:

def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C]

And a higher kinded version would look like this:

def map2K[F[_], G[_], H[_]](af: A[F], ag: A[G])(f: Tuple2K[F, G, ?] ~> H): A[H]

If you haven’t guessed yet Tuple2K is just a higher kinded version of Tuple2:

type Tuple2K[F[_], G[_], A] = (F[A], G[A])

Unfortunately Mainecoon doesn’t have an ApplyK type class that gives us this map2K operation, but it gives the next best thing! A higher-kinded Semigroupal, which when combined with the higher kinded Functor gives us that higher kinded Apply type class. It’s called CartesianK (because cats Semigroupal used to be called Cartesian, but is renamed to SemigroupalK in the next version) and looks like this:

@typeclass 
trait CartesianK[A[_[_]]] {
  def productK[F[_], G[_]](af: A[F], ag: A[G]): A[Tuple2K[F, G, ?]]
}

Now just like you can define map2 using map and product we can do the same for map2K:

def map2K[F[_], G[_], H[_]](af: A[F], ag: A[G])(f: Tuple2K[F, G, ?] ~> H): A[H] =
  productK(af, ag).mapK(f)

Putting it all together

Okay, after that quick detour, let’s have a look at how can make use of these type classes.

If we look at what we have and how we’d like to use the map2K function, we can infer the rest that we need quite easily.

We have an Alg[F] and a Alg[? => M], and we want an Alg[StateT[F, M, ?]], so given those two as the inputs to map2K, all that seems to be missing is the natural transformation Tuple2K[F, ? => M, ?] ~> StateT[F, M, ?]. Nice! As so often, the types guide us and show us the way.

Well let’s try to define just that:

new (Tuple2K[F, ? => M, ?] ~> StateT[F, M, ?]) {
  def apply[A](fa: Tuple2K[F, ? => M, ?]): StateT[F, M, A] =
    StateT(m => F.map(fa.first)(a => M.combine(fa.second(a), m) -> a))
}

This looks good, but actually has a problem, to get an Alg[F] from rebuild we give it an M and an interpreter Alg[F]. The interpreter isn’t really a problem, but the M can prove problematic as we need to give it to the rebuild function after each monadic step to always receive the latest state. If we look at our natural transformation above, that function will never receive the newest state. So what can we do about this? Well, we could be a bit more honest about our types:

type FunctionM[A] = M => F[A]
def rebuild(interp: Alg[F]): Alg[FunctionM]

Hey, now we’re getting there. This works, but if we look into some of the data types provided by Cats we can acutally see that this is just Kleisli or ReaderT, so our rebuild should actually look like this:

def rebuild(interp: Alg[F]): Alg[Kleisli[F, M, ?]]

And now, we can easily implement a correct version of that natural transformation from earlier:

new (Tuple2K[Kleisli[F, M, ?], ? => M, ?] ~> StateT[F, M, ?]) {
  def apply[A](fa: Tuple2K[Kleisli[F, M, A], ? => M, ?]): StateT[F, M, A] =
    StateT(m => F.map(fa.first.run(m))(a => (fa.second(a) |+| m) -> a))
}

Cool, then let us also adjust the rebuild function we created for KVStore:

def rebuild(interp: KVStore[F]): KVStore[Kleisli[F, M, ?]] = new KVStore[Kleisli[F, M, ?]] {
  def get(key: String): Kleisli[F, Cache, Option[String]] = Kleisli(m => m.get(key) match {
    case o @ Some(_) => Monad[F].pure(o)
    case None => interp.get(key)
  })

  def put(key: String, a: String): Kleisli[F, Cache, Unit] =
    Kleisli(m => interp.put(key, a))
}

It’s stayed pretty much the same, we just needed to wrap the whole thing in a Kleisli and we’re good!

Now we can go ahead and define the full function signature:

def optimize[Alg[_[_]]: FunctorK: CartesianK, F[_]: Monad, M: Monoid, A]
  (program: MonadProgram[Alg, A])
  (extract: Alg[? => M])
  (rebuild: Alg[F] => Alg[Kleisli[F, M, ?]]): Alg[F] => F[A] = { interpreter =>
  
    val tupleToState = new (Tuple2K[Kleisli[F, M, ?], ? => M, ?] ~> StateT[F, M, ?]) {
      def apply[A](fa: Tuple2K[Kleisli[F, M, A], ? => M, A]): StateT[F, M, A] =
        StateT(m => F.map(fa.first.run(m))(a => (fa.second(a) |+| m) -> a))
    }

    val withState: Alg[StateT[F, M, ?]] =
      map2K(extract(interpreter), rebuild))(tupleToState)

    program(withState).runEmptyA
  
  }

That is all, we’ve got a fully polymorphic function that can optimize monadic programs.

Let’s use it!

optimize(program)(extract)(rebuild)
  .apply(printInterpreter)
  .unsafeRunSync()

Now, when we run this, it should be exactly the same result as when we ran it earlier using the direct StateT interpreter, but the resulting code is much cleaner. However, it does have the drawback that you’ll now need additional constraints for every algebra to use this function. That said though, one of the cool features of Mainecoon is that it comes with auto-derivation. Meaning we can just add an annotation to any of our algebras and it will automatically derive the FunctorK and CartesianK instances.

In fact, that is exactly how I defined those two instances for the KVStore algebra:

@autoFunctorK
@autoCartesianK
trait KVStore[F[_]] { ... }

This makes it fairly easy to use these extra type classes and helpts mitigate the drawbacks I mentioned.

Conclusions

Today we’ve seen a way to make optimizing monadic tagless final programs easier and intuitive, all the code is taken from the sphynx library and can be found right here, but might still be subject to change, because designing a good API is hard.

What do you think about this optimization scheme? Maybe you just prefer using StateT and being done with it, or maybe you like to use a typeclass based approach like the one we used last time?

Would love to hear from you all in the comments!

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

Testing in the wild

$
0
0

Writing tests seems like a wonderful idea in theory but real systems can be a real pain to test. Today I want to show a few tips on how to use specs2 + ScalaCheck to make some real-world testing somewhat bearable.

I am currently refactoring a big piece of code. Such refactoring is more like a small rewrite and some of our previous tests also have to be rewritten from scratch. I will try to introduce you to the problem first.

Creating articles

The system-du-jour is called “Panda” (we have animal names for many of our services in my team) and is tasked with the creation of articles on our legacy platform. An article is already a complicated beast. We have 3 levels of descriptions:

  • Model: the description for a pair of RunFast shoes, with the brand, the gender it applies to, the size chart it uses and so on
  • Config: the description for a specific combination of colours - black RunFast -, images, material,… There can be several configs per model
  • Simple: the description of a specific size - 37, 38, 39 -, price, stock, EAN (European Article Number),… There can be several simples per config

This data can be created in our legacy catalog by calling a bunch of SOAP (yes you read that right) APIs and getting back, at each level, some identifiers for the model, the configs, the simples.

This in itself can already be quite complicated and the creation of an article could fail in many ways. But it gets a lot more complex considering that:

  1. we need the service to be idempotent and not try to recreate an existing model/config/simple twice if we receive the same event twice (we are using Kafka as our events system)

  2. the articles sent by a merchant can be created incrementally, so some parts of the model/config/simple might have been already created in a previous call

  3. we are not the only ones creating articles in the system! Indeed, our team creates articles coming from external merchants but there is also an internal “wholesale” department buying their own articles and creating them in the catalog. In that case a merchant might add a new config to an existing model or some simples to an existing config

  4. any step in the process could break and we have no support for transactions making sure that everything is created at once

So many things which can go wrong, how would you go about testing it?

Combinations, the key to testing

After I started rewriting the tests I realized that our current approach was barely scratching the surface of all the possible combinations. In a similar case your first thought should be “ScalaCheck”! But this time I am going to use ScalaCheck with a twist. Instead of only modelling input data (model/config/simples) I am also modelling the system state:

  • we have a “mapping table” to store merchant articles that have already been created. In which states can it be?
  • the legacy catalog can also be in many different states: does a particular model/config/simple already exist or not?

If we translate this into a specs2 + ScalaCheck specification, we get a property like this:

class ArticleServiceSpec extends Specification with ScalaCheck { def is = s2"""

  <insert long description of the problem and what we want to test>

  run tests for model creation $modelCreation

"""

  def modelCreation = prop { (reviewed: Article, catalog: TestCatalog, mappings: TestMappings) =>

    ok // for now


  }.setGen1(genArticleOneConfig)
}

We are creating a ScalaCheck property, with a specs2 method prop which gives us some additional power over row ScalaCheck properties. One thing we do here is to restrict the kind of generated Article to articles containing only one new configuration because we want to focus first on all the possible cases of model creation. So we pass a specific generator to the property, just for the first argument with setGen1.

Then, as you can see above, we return ok which is a specs2 result. This is because prop allows us to return anything that specs2 recognizes as a Result (with the org.specs2.execute.AsResult typeclass) and then we are not limited to booleans in our ScalaCheck properties but we can use specs2 matchers as well (we are going use this in the next step).

Now, for testing we need to do the following:

  1. capture the state before the article creation
  2. execute the article creation
  3. capture the state after the article creation
  4. compare the resulting state to expected values
  val before = createBeforeState(reviewed, catalog, mappings)
  val result = run(createService(catalog, mappings).createArticles[R](reviewed))
  val after  = createAfterState(reviewed, catalog, mappings, result)

What are BeforeState and AfterState? They are custom case classes modelling the variables we are interested in:

 case class BeforeState(
   modelIdProvided:       Boolean,
   modelNeedsToBeCreated: Boolean,
   modelExistsInCatalog:  Boolean,
   mappingExists:         Boolean)

 case class AfterState(
   modelExistsInCatalog: Boolean,
   mappingExists:        Boolean,
   exception:            Option[Throwable])

The first 2 variables of BeforeState are a bit curious. The first one gives us a ModelId if upstream systems know that a model already exist. Then how could modelNeedsToBeCreated be true? Well, the events we receive don’t rule out this possibility. This is the current state of our domain data and arguably we should model things differently and reject malformed events right away. This is where the saying “Listen to your tests” comes in :-).

If we count the number of combinations we end up with 16 possibilities for our “before state” and 8 possible outcomes. How can we represent all those combinations in our test?

DataTables

Specs2 offers to possibility to create tables of data directly inside the code for better readability of actual and expected values when you have lots of different possible combinations. Here is what we can do here

  val results =
  "#" | "model-id" | "create" | "in catalog" | "mapping" || "in catalog"  | "mapping" | "exception" | "comment" |
  1   !  true      ! true     ! true         ! true      !! true          ! true      ! false       ! "no model is created, because it can be found in the catalog, creation data is ignored" |
  2   !  true      ! true     ! true         ! false     !! true          ! true      ! false       ! "we just updated the mapping" |
  3   !  true      ! true     ! false        ! true      !! false         ! true      ! true        ! "the config creation must fail, no existing model" |
  4   !  true      ! true     ! false        ! false     !! true          ! true      ! false       ! "the given model-id is ignored (a warning is logged)" |
  5   !  true      ! false    ! true         ! true      !! true          ! true      ! false       ! "no model is created, because it can be found in the catalog" |
  6   !  true      ! false    ! true         ! false     !! true          ! false     ! false       ! "the mappings are not updated because we did not create the model" |
  7   !  true      ! false    ! false        ! true      !! false         ! true      ! true        ! "no corresponding model in the catalog" |
  8   !  true      ! false    ! false        ! false     !! false         ! false     ! true        ! "no corresponding model in the catalog" |
  9   !  false     ! true     ! true         ! true      !! true          ! true      ! false       ! "we use the mapping table to retrieve the model id and the catalog for the model" |
  10  !  false     ! true     ! true         ! false     !! true          ! true      ! false       ! "in this case the model already exists in the catalag but we have no way to know" |
  11  !  false     ! true     ! false        ! true      !! false         ! true      ! true        ! "the mapping exists but not the data in the catalog" |
  12  !  false     ! true     ! false        ! false     !! true          ! true      ! false       ! "regular model + config creation case" |
  13  !  false     ! false    ! true         ! true      !! true          ! true      ! true        ! "there is no model id and no creation data" |
  14  !  false     ! false    ! true         ! false     !! true          ! false     ! true        ! "the model exists in the catalog but we have no way to retrieve it" |
  15  !  false     ! false    ! false        ! true      !! false         ! true      ! true        ! "model id found in the mapping but not in the catalog" |
  16  !  false     ! false    ! false        ! false     !! false         ! false     ! true        ! "not enough data to create the model nor the mapping"

 checkState(before, after, parseTable(results))

This looks like a strange piece of code but this is actually all valid Scala syntax! results is a specs2 DataTable created out of:

  • a header where column names are separated with |
  • rows that are also separated with |
  • cells on each row, separated with !

We can also use || and !! as separators and we use this possibility here to visually distinguish input columns from expected results columns.

Running the tests

The table above is like a big “truth table” for all our input conditions. Running a test consists in:

  1. using the ‘before state’ to locate one of the row
  2. getting the expected ‘after state’ from the expected columns
  3. comparing the actual ‘after state’ with the expected one

The funny thing is that before executing the test I did not exactly know what the code would actually do! So I just let the test guide me. I put some expected values, run the test and in case of a failure, inspect the input values, think hard about why the code is not behaving the way I think it should.

One question comes to mind: since this is a ScalaCheck property, how can we be sure we hit all the cases in the table? The first thing we can do is to massively increase the number of tests that are going to be executed for this property, like 10000. With specs2 you have many ways to do this. You can set the minTestsOk ScalaCheck property directly in the code:

def modelCreation = prop { (reviewed: Article, catalog: TestCatalog, mappings: TestMappings) =>
 ...
}.setGen1(genArticleOneConfig).set(minTestsOk = 10000)

But you can also do it from sbt:

sbt> testOnly *ArticleServiceSpec -- scalacheck.mintestsok 10000

This is quite cool because this means that you don’t have to recompile the code if you just want to run a ScalaCheck property with more tests.

Checking the results

As I wrote, when a specific combination would fail I had to inspect the inputs/outputs and think hard, maybe my expectations are wrong and I needed to change the expected values? To this end I added a “line number” column to the table and reported it in the result:

 [error]  > On line 6
 [error]
 [error]  Before
 [error]    model id set:             true
 [error]    model creation data set:  false
 [error]    model exists in catalog : true
 [error]    model id mapping exists:  false
 [error]
 [error]  After
 [error]    model exists in catalog:  true
 [error]      expected:               true
 [error]
 [error]    model id mapping exists:  false
 [error]      expected:               false
 [error]
 [error]    exception thrown:         None
 [error]      expected:               Some

This reporting is all done in the checkState method which is:

  • doing the comparison between actual and expected values
  • displaying the before / after states
  • displaying the difference between expected and actual values

Actually I even enhanced the display of actual/expected values by coloring them in green or red in the console, using one of specs2 helper classes org.specs2.text.AnsiColors:

import org.specs2.text.AnsiColors

def withColor[A](actual: A, expected: A, condition: (A, A) => Boolean = (a:A, e:A) => a == e): String =
  // color the expected value in green or red, depending on the test success
  color(expected.toString, if (condition(actual, expected)) green else red)

withColor(after.modelExistsInCatalog, expected.modelExistsInCatalog)

Both the line numbering and the coloring really helps in fixing issues fast!

Replaying tests

A vexing issue with property-based testing is that being random, it will generate random failures every time you re-run a property. So you can’t re-run a property with the exact same input data. But that was before ScalaCheck 1.14! Now we can pass the seed that is used by the random generator to faithfully re-run a failing test. Indeed when a property fails, specs2 will display the current seed value:

[error]  The seed is 1tRQ5-jdfEABEXz1y62Cs0C4vNJQKyXps9eWvbjJPSI=

And you can pass this value on the command line to re-run with exactly the failing input data:

sbt> testOnly *ArticleServiceSpec -- scalacheck.seed 1tRQ5-jdfEABEXz1y62Cs0C4vNJQKyXps9eWvbjJPSI=

This is super-convenient for debugging!

Comments

Finally when a given row in the table passes, there is a comment column to register the reason for this specific outcomes so that future generations have a sense of why the code is behaving that way. In that sense this whole approach is a bit like having “golden tests” which are capturing the behaviour of the system as a series of examples

Conclusion

This post shows how we can leverage features from both specs2 and ScalaCheck to make our tests more exhaustive, more readable, more debuggable. The reality is still more complicated than this:

  • the total number of combinations would make our table very large. So there are actually several tables (one for model creation, one for config creation,…) where we assume that some variables are fixed while others can move

  • specs2 datatables are currently limited to 10 columns. The DataTable code is actually code generated and the latest version only has 10 columns. One easy first step would be to generate more code (and go up to the magic 22 number for example) or to re-implement this functionality as some kind of HList

  • the input state is not trivial to generate because the objects are dependent. The ModelId of a generated model must be exactly the same as the one used in the Mappings component to register that a model has already been created. So in reality the 2 generators for Article and Mappings are not totally independent

  • the Arbitrary instance for Article can give us articles with 5 Configs and 10 Simples but for this test, one Config and one Simple are enough. Unfortunately we miss a nice language to express those generation condition and easily tweak the default Arbitrary[Article] (I will explore a solution to this problem during the next Haskell eXchange)

  • why are we even using ScalaCheck to generate all the cases since we already statically know all the possible 16 input conditions? We could invert this relation and have a ScalaCheck property generated for each row of the datatable with some arbitrary data for the model (and some fixed data given by the current row). This would not necessarily lead to easier code to implement.

Anyway despite those remaining questions and issues I hope this post gives you some new ideas on how to be more effective when writing tests with specs2 and ScalaCheck, please comment on your own experiments!

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

Refactoring with Monads

$
0
0

I was recently cleaning up some Scala code I’d written a few months ago when I realized I had been structuring code in a very confusing way for a very long time. At work, we’ve been trying to untangle the knots of code that get written by different authors at different times, as requirements inevitably evolve. We all know that code should be made up of short, easily digestible functions but we don’t always get guidance on how to achieve that. In the presence of error handling and nested data structures, the problem gets even harder.

The goal of this blog post is to describe a concrete strategy for structuring code so that the overall flow of control is clear to the reader, even months later; and so the smaller pieces are both digestible and testable. I’ll start by giving an example function, operating on some nested data types. Then I’ll explore some ways to break it into smaller pieces. The key insight is that we can use computational effects in the form of monads (more specifically, MonadError) to wrap smaller pieces and ultimately, compose them into an understandable sequence of computations.

Example domain: reading a catalog

Let’s not worry about MonadError yet, but instead look at some example code. Consider a situation where you need to translate data from one domain model to another one with different restrictions, and controlled vocabularies. This can happen in a number of places in a program, for instance reading a database or an HTTP request to construct a domain object.

Suppose we need to read an object from a relational database. Unfortunately, rows in the table may represent objects of a variety of types so we have to read the row and build up the object graph accordingly. This is the boundary between the weakly typed wilderness and the strongly typed world within our program.

Say our database table represents a library catalog, which might have print books and ebooks. We’d like to look up a book by ID and get back a nicely typed record.

Here’s a simple table

id title author format download_type
45 Programming In Haskell Hutton, Graham print null
46 Programming In Haskell Hutton, Graham ebook epub
49 Programming In Haskell Hutton, Graham ebook pdf

We can define a simple domain model:

sealed trait Format
case object Print extends Format
case object Digital extends Format
object Format {
  def fromString(s: String): Try[Format] = ???
}

sealed trait DownloadType
case object Epub extends DownloadType
case object Pdf extends DownloadType
object DownloadType {
  def fromString(s: String): Try[DownloadType] = ???
}

sealed trait Book extends Product with Serializable {
  def id: Int
  def title: String
  def author: String
  def format: Format
}

case class PrintBook(
    id: Int,
    title: String,
    author: String,
) extends Book {
  override val format: Format = Print
}

case class EBook(
    id: Int,
    title: String,
    author: String,
    downloadType: DownloadType
) extends Book {
  override val format: Format = Digital
}

We want to be able to define a method such as:

def findBookById(id: Int): Try[Book] = ???

Monolithic function

One trivial definition of findBookById might be:

import scala.util.{Failure, Success, Try}

def findBookById(id: Int): Try[Book] = {
  // unsafeQueryUnique returns a `Try[Row]`
  DB.unsafeQueryUnique(sql"""select * from catalog where id = $id""").flatMap { row =>
    // pick out the properties every book possesses
    val id = row[Int]("id")
    val title = row[String]("title")
    val author = row[String]("author")
    val formatStr = row[String]("format")

    // now start to determine the types - get the format first
    Format.fromString(formatStr).flatMap {
      case Print =>
        // for print books, we can construct the book and return immediately
        Success(PrintBook(id, title, author))
      case Digital =>
        // for digital books we need to handle the download type
        row[Option[String]]("download_type") match {
          case None =>
            Failure(new AssertionError(s"download type not provided for digital book $id"))
          case Some(downloadStr) =>
            DownloadType.fromString(downloadStr).flatMap { dt =>
              Success(EBook(id, title, author, dt))
            }
        }
    }
  }
}

Depending on your perspective, that is arguably a long function. If you think it is not so long, pretend that the table has a number of other fields that must also be conditionally parsed to construct a Book.

Tail refactoring

One possible approach is a a strategy I’m going to call “tail-refactoring”, for lack of a better description. Basically, each function does a little work or some error checking, and then calls the next appropriate function in the chain.

You can imagine what kind of code will result. The functions are smaller, but it’s hard to describe what each function does, and functions occasionally have to carry along additional parameters that they will ignore except to pass deeper into the call chain. Let’s take a look at an example refactoring:

import scala.util.{Failure, Success, Try}

def extractEBook(
    id: Int,
    title: String,
    author: String,
    downloadTypeStrOpt: Option[String]): Try[EBook] =
  downloadTypeStrOpt match {
    case None => Failure(new AssertionError())
    case Some(downloadTypeStr) =>
      DownloadType.fromString(downloadTypeStr).flatMap { dt =>
        Success(EBook(id, title, author, dt))
      }
  }

def extractBook(
    id: Int,
    title: String,
    author: String,
    formatStr: String,
    downloadTypeStrOpt: Option[String]): Try[Book] =
  Format.fromString(formatStr).flatMap {
    case Print =>
      Success(PrintBook(id, title, author))
    case Digital =>
      extractEBook(id, title, author, downloadTypeStrOpt)
  }

def findBookById(id: Int): Try[Book] =
  DB.unsafeQueryUnique(sql"""select * from catalog where id = $id""").flatMap { row =>
    val id = row[Int]("id")
    val title = row[String]("title")
    val author = row[String]("author")
    val formatStr = row[String]("format")
    val downloadTypeStr = row[Option[String]]("download_type")
    extractBook(id, title, author, formatStr, downloadTypeStr)
  }

As you can see, this form has more manageably-sized functions, although they are still a little long. You can also see that the flow of control is distributed through all three functions, which means understanding the logic enough to modify or test it requires understanding all three functions both individually and as a whole. To follow the logic, we must trace the functions like a recursive descent parser.

Refactoring with Monads

Without throwing exceptions and catching them at the top, it’s going to be hard to do substantially better than the “tail-refactoring” approach, unless we start to make use of the fact that we’re working with Try, a data type that supports flatMap. More precisely, Try has a monad instance - recall that monads let us model computational effects that take place in sequence.

Let’s try to factor out smaller functions, each returning Try, and then use a for-comprehension to specify the sequence of operations:

import scala.util.{Failure, Success, Try}

def parseDownloadType(o: Option[String], id: Int): Try[DownloadType] = {
  o.map(DownloadType.fromString)
    .getOrElse(Failure(new AssertionError(s"download type not provided for digital book $id")))
}

def findBookById(id: Int): Try[Book] =
  for {
    row <- DB.unsafeQueryUnique(sql"""select * from catalog where id = $id""")
    format <- Format.fromString(row[String]("format"))
    id = row[Int]("id")
    title = row[String]("title")
    author = row[String]("author")
    book <- format match {
      case Print =>
        Success(PrintBook(id, title, author))
      case Digital =>
        parseDownloadType(row[Option[String]]("download_type"), id)
          .map(EBook(id, title, author, _))
    }
  } yield book

It’s less code, the functions are smaller, and the top-level function dictates the entire flow of control. No function takes more than 2 arguments. These are testable, understandable functions. This version really shows the power of using monads to sequence computation.

Now we are truly making use of the fact that Try has a monad instance and not just another container class. We can simply describe the “happy path” and trust Try to short-circuit computation if something erroneous or unexpected occurs. In that case, Try captures the error and stops computation there. The code does this without the need for explicit branching logic.

Abstracting effect type

Now, let’s take this one step further - here’s where we achieve buzzword compliance. Let’s abstract away from the effect, Try, and instead make use of MonadError. This lets us use a more diverse set of effect types, from IO to Task, so we can execute our function in whatever asynchronous context we wish. This has the feel of a tagless final strategy (although we aren’t worrying about describing interpreters here).

Here we go:

import cats.MonadError
import cats.implicits._

def parseDownloadType[F[_]](o: Option[String], id: Int)(
    implicit me: MonadError[F, Throwable]): F[DownloadType] = {
  me.fromOption(o, new AssertionError(s"download type not provided for digital book $id"))
    .flatMap(s => me.fromTry(DownloadType.fromString(s)))
}

def findBookById[F[_]](id: Int)(implicit me: MonadError[F, Throwable]): F[Book] =
  for {
    row <- DB.queryUnique[F](sql"""select * from catalog where id = $id""")
    format <- me.fromTry(Format.fromString(row[String]("format")))
    id = row[Int]("id")
    title = row[String]("title")
    author = row[String]("author")
    book <- format match {
      case Print =>
        me.pure(PrintBook(id, title, author))
      case Digital =>
        parseDownloadType[F](row[Option[String]]("downloadType"), id)
          .map(EBook(id, title, author, _))
    }
  } yield book

The code isn’t much more complicated than the version using Try but it adds a lot of flexibility. In a synchronous context, we could still use Try. In that case, however, the database call is executed eagerly, which means the function isn’t referentially transparent. We can make the function referentially transparent by using a monad such as IO or Task as the effect type and delaying the evaluation of the database call until “the end of the universe”.

In this example, pay attention to the use of fromOption and fromTry, which adapt Option and Try to F. If you are using existing APIs that aren’t already generalized to MonadError these methods adapt common error types, but require very little ceremony to use.

Refactoring strategy

When faced with a similar refactoring problem, consider whether you can break the problem into a sequence of independently executable steps, each of which can be wrapped in a monad. If so, begin by describing the control flow in your refactored function with a monadic for-comprehension. Don’t define the individual functions that comprise the steps of the for-comprehension until you have filled in the yield at the end. You can use pseudocode or stubs to minimize the amount of code churn at the beginning. This is a great time to shuffle steps around and work out exactly what arguments are needed and when, as well as where they are coming from.

Once the top level function looks plausible, begin implenting the steps of the for-comprehension. You can replace the stubs or pseudocode you wrote by refactoring code from your original function. If the original code did not operate in a monadic context, recall that you can convert a simple function A => B to F[A] => F[B] using lift (thanks, Functor!). This makes converting your existing code even easier.

Conclusion

In this post, we have seen how we can use monads as an aid in refactoring code to improve both readability and testability. We have also demonstrated that we can do this in many cases without needing to specify the monad in use a priori. As a result, we gain the flexibility to choose the appropriate monad for our application, independently of the program logic.

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

Http4s error handling with Cats Meow MTL

$
0
0

As a longtime http4s user I keep on learning new things and I’m always trying to come up with the best practices for writing http applications. This time I want to talk about my latest achievements in error handling within the context of an http application where it basically means mapping each business error to the appropiate http response.

So let’s get started by putting up an example of an http application with three different endpoints that interacts with a UserAlgebra that may or may not fail with some specific errors.

If you are one of those who don’t like to read and prefer to jump straight into the code please find it here :)

User Algebra

We have a simple UserAlgebra that let us perform some actions such as finding and persisting users.

case class User(username: String, age: Int)
case class UserUpdateAge(age: Int)

trait UserAlgebra[F[_]] {
  def find(username: String): F[Option[User]]
  def save(user: User): F[Unit]
  def updateAge(username: String, age: Int): F[Unit]
}

And also an ADT of the possible errors that may arise. I’ll explain later in this post why it extends Exception.

sealed trait UserError extends Exception
case class UserAlreadyExists(username: String) extends UserError
case class UserNotFound(username: String) extends UserError
case class InvalidUserAge(age: Int) extends UserError

User Interpreter

And here we have a simple interpreter for our UserAlgebra for demonstration purposes so you can have an idea on how the logic would look like. In a real-life project an interpreter will more likely connect to a database instead of using an in-memory representaion based on Ref.

import cats.effect.Sync
import cats.effect.concurrent.Ref
import cats.syntax.all._

object UserInterpreter {

  def create[F[_]](implicit F: Sync[F]): F[UserAlgebra[F]] =
    Ref.of[F, Map[String, User]](Map.empty).map { state =>
      new UserAlgebra[F] {
        private def validateAge(age: Int): F[Unit] =
          if (age <= 0) F.raiseError(InvalidUserAge(age)) else F.unit

        override def find(username: String): F[Option[User]] =
          state.get.map(_.get(username))

        override def save(user: User): F[Unit] =
          validateAge(user.age) *>
            find(user.username).flatMap {
              case Some(_) =>
                F.raiseError(UserAlreadyExists(user.username))
              case None =>
                state.update(_.updated(user.username, user))
            }

        override def updateAge(username: String, age: Int): F[Unit] =
          validateAge(age) *>
            find(username).flatMap {
              case Some(user) =>
                state.update(_.updated(username, user.copy(age = age)))
              case None =>
                F.raiseError(UserNotFound(username))
            }
      }
    }

}

Http Routes

The following implementation of UserRoutes applies the tagless final encoding and the concept of “abstracting over the effect type” where we do not commit to a particular effect until the edge of our application.

import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s._
import org.http4s.circe._
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.dsl.Http4sDsl

class UserRoutes[F[_]: Sync](userAlgebra: UserAlgebra[F]) extends Http4sDsl[F] {

  val routes: HttpRoutes[F] = HttpRoutes.of[F] {

    case GET -> Root / "users" / username =>
      userAlgebra.find(username).flatMap {
        case Some(user) => Ok(user.asJson)
        case None => NotFound(username.asJson)
      }

    case req @ POST -> Root / "users" =>
      req.as[User].flatMap { user =>
        userAlgebra.save(user) *> Created(user.username.asJson)
      }

    case req @ PUT -> Root / "users" / username =>
      req.as[UserUpdateAge].flatMap { userUpdate =>
        userAlgebra.updateAge(username, userUpdate.age) *> Ok(username)
      }
  }

}

Now this particular implementation is missing a very important part: error handling. If we use the UserAlgebra’s interpreter previously defined we will clearly miss the three errors defined by the UserError ADT.

NOTE: If you are not familiar with these concepts make sure you check out my talk at Scala Matsuri early this year where I also talk about error handling in http applications using the Http4s library.

Http Error Handling

Okay let’s just go ahead and add some error handling to our http route by taking advantange of the MonadError instance defined by our constraint Sync[F] and making use of the syntax provided by cats:

class UserRoutesAlt[F[_]: Sync](userAlgebra: UserAlgebra[F]) extends Http4sDsl[F] {

  val routes: HttpRoutes[F] = HttpRoutes.of[F] {

    case GET -> Root / "users" / username =>
      userAlgebra.find(username).flatMap {
        case Some(user) => Ok(user.asJson)
        case None => NotFound(username.asJson)
      }

    case req @ POST -> Root / "users" =>
      req.as[User].flatMap { user =>
        userAlgebra.save(user) *> Created(user.username.asJson)
      }.handleErrorWith {
        case UserAlreadyExists(username) => Conflict(username.asJson)
      }

    case req @ PUT -> Root / "users" / username =>
      req.as[UserUpdateAge].flatMap { userUpdate =>
        userAlgebra.updateAge(username, userUpdate.age) *> Ok(username.asJson)
      }.handleErrorWith {
        case InvalidUserAge(age) => BadRequest(s"Invalid age $age".asJson)
      }
  }

}

Now we can say this implementation is quite elegant! We are handling and mapping business errors to the according http response and our code compiles without any warning whatsoever. But wait… We are not handling the UserNotFound error and the compiler didn’t tell us about it! That’s not cool and we as functional programmers believe in types because we can know what a function might do just by looking at the types but here it seems we hit the wall.

The problem is that our constraint of type Sync from cats-effect has a MonadError instance with its type error fixed as Throwable. So the compiler can’t help us here since this type is too generic. And we can’t add a constraint for MonadError[F, UserError] because we would get an “ambigous implicits” error with two instances of MonadError in scope.

So, what can we do about it?

Next level MTL: Optics

I heard sometime ago about Classy Optics (Lenses, Prisms, etc) when I was learning Haskell and watched this amazing talk by George Wilson but I never got to use this concept in Scala until now!

Well first, let me give you a quick definition of Lenses and Prisms. In a few words we can define:

  • Lenses as getters and setters that compose making the accessing of nested data structure’s fields quite easy.
  • Prisms as first-class pattern matching that let us access branches of an ADT and that also compose.

And Classy Optics as the idea of “associate with each type a typeclass full of optics for that type”.

So what am I talking about and how can these concepts help us solving the http error handling problem?

Remember that I defined the UserError ADT by extending Exception?

sealed trait UserError extends Exception
case class UserAlreadyExists(username: String) extends UserError
case class UserNotFound(username: String) extends UserError
case class InvalidUserAge(age: Int) extends UserError

Well there’s a reason! By making UserError a subtype of Exception (and by default of Throwable) we can take advantage of Prisms by going back and forth in the types. See what I’m going yet?

UserRoute has a Sync[F] constraint, meaning that we have available a MonadError[F, Throwable] instance, but we would like to have MonadError[F, UserError] instead to leverage the Scala compiler. The caveat is that the error types need to be of the same family so we can derive a Prism that can navigate the errors types in one direction or another. But how do we derive it?

Cats Meow MTL

Fortunately our friend Oleg Pyzhcov has created this great library named meow-mtl that makes heavy use of Shapeless in order to derive Lenses and Prisms and it provides instances for some cats-effect compatible datatypes.

And two of the supported typeclasses are ApplicativeError and MonadError as long as the error type is a subtype of Throwable to make it compatible with cats-effect. So we can do something like this:

import cats.MonadError
import cats.effect.IO
import com.olegpy.meow.hierarchy._ // All you need is this import!
import scala.util.Random

case class CustomError(msg: String) extends Throwable

def customHandle[F[_], A](f: F[A], fallback: F[A])(implicit ev: MonadError[F, CustomError]): F[A] =
  f.handleErrorWith(_ => fallback)

val io: IO[Int] = IO(Random.nextInt(2)).flatMap { case 1 => IO.raiseError(new Exception("boom")) }
customHandle(io, IO.pure(123))

Generalizing Http Error Handling

Now back to our use case. We can’t have a MonadError[F, UserError] constraint because there’s already a MonadError[F, Throwable] in scope given our Sync[F] constraint. But it turns out we can make this work if we also abstract over the error handling by introducing an HttpErrorHandler algebra where the error type is a subtype of Throwable.

trait HttpErrorHandler[F[_], E <: Throwable] {
  def handle(routes: HttpRoutes[F]): HttpRoutes[F]
}

object HttpErrorHandler {
  def apply[F[_], E <: Throwable](implicit ev: HttpErrorHandler[F, E]) = ev
}

UserRoutes can now have an additional constraint of type HttpErrorHandler[F, UserError] so we clearly know what kind of errors we are dealing with and can have the Scala compiler on our side.

class UserRoutesMTL[F[_]: Sync](userAlgebra: UserAlgebra[F])(implicit H: HttpErrorHandler[F, UserError]) extends Http4sDsl[F] {

  private val httpRoutes: HttpRoutes[F] = HttpRoutes.of[F] {

    case GET -> Root / "users" / username =>
      userAlgebra.find(username).flatMap {
        case Some(user) => Ok(user.asJson)
        case None => NotFound(username.asJson)
      }

    case req @ POST -> Root / "users" =>
      req.as[User].flatMap { user =>
        userAlgebra.save(user) *> Created(user.username.asJson)
      }

    case req @ PUT -> Root / "users" / username =>
      req.as[UserUpdateAge].flatMap { userUpdate =>
        userAlgebra.updateAge(username, userUpdate.age) *> Created(username.asJson)
      }
  }

  val routes: HttpRoutes[F] = H.handle(httpRoutes)

}

We are basically delegating the error handling (AKA mapping business errors to appropiate http responses) to a specific algebra.

We also need an implementation for this algebra in order to handle errors of type UserError but first we can introduce a RoutesHttpErrorHandler object that encapsulates the repetitive task of handling errors given an HttpRoutes[F]:

import cats.ApplicativeError
import cats.data.{Kleisli, OptionT}

object RoutesHttpErrorHandler {
  def apply[F[_], E <: Throwable](routes: HttpRoutes[F])(handler: E => F[Response[F]])(implicit ev: ApplicativeError[F, E]): HttpRoutes[F] =
    Kleisli { req: Request[F] =>
      OptionT {
        routes.run(req).value.handleErrorWith { e => handler(e).map(Option(_)) }
      }
    }
}

And our implementation:

class UserHttpErrorHandler[F[_]](implicit M: MonadError[F, UserError]) extends HttpErrorHandler[F, UserError] with Http4sDsl[F] {
  private val handler: UserError => F[Response[F]] = {
    case InvalidUserAge(age) => BadRequest(s"Invalid age $age".asJson)
    case UserAlreadyExists(username) => Conflict(username.asJson)
    case UserNotFound(username) => NotFound(username.asJson)
  }

  override def handle(routes: HttpRoutes[F]): HttpRoutes[F] =
    RoutesHttpErrorHandler(routes)(handler)
  }

If we forget to handle some errors the compiler will shout at us “match may not be exhaustive!” That’s fantastic :)

Wiring all the components

And the last part will be the wiring of all these components where we need to include the meow-mtl import to figure out the derivation of the instances we need in order to make this work. It’ll look something like this if using cats.effect.IO:

import com.olegpy.meow.hierarchy._

implicit val userHttpErrorHandler: HttpErrorHandler[IO, UserError] = new UserHttpErrorHandler[IO]

UserInterpreter.create[IO].flatMap { UserAlgebra =>
  val routes = new UserRoutesMTL[IO](UserAlgebra)
  IO.unit // pretend this is the rest of your program
}

Final thoughts

This is such an exciting time to be writing pure functional programming in Scala! The Typelevel ecosystem is getting richer and more mature, having an amazing set of libraries to solve business problems in an elegant and purely functional way.

I hope you have enjoyed this post and please do let me know if you know of better ways to solve this problem in the comments!

And last but not least I would like to thank all the friendly folks I hang out with in the cats-effect, cats, fs2 and http4s Gitter channels for all the time and effort they put (for free) into making this community an amazing space.

Licensing

Unless otherwise noted, all content is licensed under a Creative Commons Attribution 3.0 Unported License.

Viewing all 285 articles
Browse latest View live


Latest Images