Yesterday we skimmed two papers by Jeremy Gibbons and quickly looked at origami programming and applicative traversal. Instead of reading something, why don’t we focus on using Scalaz today.
Scalaz makes heavy use of implicits. Both as a user and an extender of the library, it’s important to have general idea on where things are coming from. Let’s quickly review Scala’s imports and implicits!
In Scala, imports are used for two purposes: 1. To include names of values and types into the scope. 2. To include implicits into the scope.
Implicits are for 4 purposes that I can think of: 1. To provide typeclass instances. 2. To inject methods and operators. (static monkey patching) 3. To declare type constraints. 4. To retrieve type information from compiler.
Implicits are selected in the following precedence: 1. Values and converters accessible without prefix via local declaration, imports, outer scope, inheritance, and current package object. Inner scope can shadow values when they are named the same. 2. Implicit scope. Values and converters declared in companion objects and package object of the type, its parts, or super types.
Now let’s see what gets imported with import scalaz._
.
First, the names. Typeclasses like Equal[A]
and Functor[F[_]]
are implemented as trait, and are defined under scalaz
package. So instead of writing scalaz.Equal[A]
we can write Equal[A]
.
Next, also the names, but type aliases. scalaz
’s package object declares most of the major type aliases like @@[T, Tag]
and Reader[E, A]
, which is treated as a specialization of ReaderT
transformer. Again, these can also be accessed as scalaz.Reader[E, A]
if you want.
Finally, idInstance
is defined as typeclass instance of Id[A]
for Traverse[F[_]]
, Monad[F[_]]
etc, but it’s not relevant. By virtue of declaring an instance within its package object it will be available, so importing doesn’t add much. Let’s check this:
scala> scalaz.Monad[scalaz.Id.Id]
res1: scalaz.Monad[scalaz.Id.Id] = scalaz.IdInstances$$anon$1@fc98c94
No import needed, which is a good thing. So, the merit of import scalaz._
is for convenience, and it’s optional.
What then is import Scalaz._
doing? Here’s the definition of Scalaz
object:
package scalaz
object Scalaz
extends StateFunctions // Functions related to the state monad
with syntax.ToTypeClassOps // syntax associated with type classes
with syntax.ToDataOps // syntax associated with Scalaz data structures
with std.AllInstances // Type class instances for the standard library types
with std.AllFunctions // Functions related to standard library types
with syntax.std.ToAllStdOps // syntax associated with standard library types
with IdInstances // Identity type and instances
This is quite a nice way of organizing the imports. Scalaz
object itself doesn’t define anythig and it just mixes in the traits. We are going to look at each traits in detail, but they can also be imported a la carte, dim sum style. Back to the full course.
Remember, import brings in names and implicits. First, the names. StateFunctions
defines several functions:
package scalaz
trait StateFunctions {
def constantState[S, A](a: A, s: => S): State[S, A] = ...
def state[S, A](a: A): State[S, A] = ...
def init[S]: State[S, S] = ...
def get[S]: State[S, S] = ...
def gets[S, T](f: S => T): State[S, T] = ...
def put[S](s: S): State[S, Unit] = ...
def modify[S](f: S => S): State[S, Unit] = ...
def delta[A](a: A)(implicit A: Group[A]): State[A, A] = ...
}
By bringing these functions in we can treat get
and put
like a global function. Why? This enables DSL we saw on day 7:
for {
xs <- get[List[Int]]
_ <- put(xs.tail)
} yield xs.head
Second, the names again. std.AllFunctions
is actually a mixin of traits itself:
package scalaz
package std
trait AllFunctions
extends ListFunctions
with OptionFunctions
with StreamFunctions
with math.OrderingFunctions
with StringFunctions
object AllFunctions extends AllFunctions
Each of the above trait bring in various functions into the scope that acts as a global function. For example, ListFunctions
bring in intersperse
function that puts a given element in ever other position:
scala> intersperse(List(1, 2, 3), 7)
res3: List[Int] = List(1, 7, 2, 7, 3)
It’s ok. Since I personally use injected methods, I don’t have much use to these functions.
Although it’s named IdInstances
, it also defines the type alias Id[A]
as follows:
type Id[+X] = X
That’s it for the names. Imports can bring in implicits, and I said there are four uses for the implicits. We mostly care about the first two: typeclass instances and injected methods and operators.
Thus far, I have been intentionally conflating the concept of typeclass instances and method injection (aka enrich my library). But the fact that List
is a Monad
and that Monad
introduces >>=
operator are two different things.
One of the most interesting design of Scalaz 7 is that it rigorously separates the two concepts into “instance” and “syntax.” Even if it makes logical sense to some users, the choice of symbolic operators can often be a point of contention with any libraries. Libraries and tools such as sbt, dispatch, and specs introduce its own DSL, and their effectiveness have been hotly debated. To make the matter complicated, injected methods may conflict with each other when more than one DSLs are used together.
std.AllInstances
is a mixin of typeclass instances for built-in (std
) data structures:
package scalaz.std
trait AllInstances
extends AnyValInstances with FunctionInstances with ListInstances with MapInstances
with OptionInstances with SetInstances with StringInstances with StreamInstances with TupleInstances
with EitherInstances with PartialFunctionInstances with TypeConstraintInstances
with scalaz.std.math.BigDecimalInstances with scalaz.std.math.BigInts
with scalaz.std.math.OrderingInstances
with scalaz.std.util.parsing.combinator.Parsers
with scalaz.std.java.util.MapInstances
with scalaz.std.java.math.BigIntegerInstances
with scalaz.std.java.util.concurrent.CallableInstances
with NodeSeqInstances
// Intentionally omitted: IterableInstances
object AllInstances extends AllInstances
Next are the injected methods and operators. All of them are defined under scalaz.syntax
package. syntax.ToTypeClassOps
introduces all the injected methods for typeclasses:
package scalaz
package syntax
trait ToTypeClassOps
extends ToSemigroupOps with ToMonoidOps with ToGroupOps with ToEqualOps with ToLengthOps with ToShowOps
with ToOrderOps with ToEnumOps with ToMetricSpaceOps with ToPlusEmptyOps with ToEachOps with ToIndexOps
with ToFunctorOps with ToPointedOps with ToContravariantOps with ToCopointedOps with ToApplyOps
with ToApplicativeOps with ToBindOps with ToMonadOps with ToCojoinOps with ToComonadOps
with ToBifoldableOps with ToCozipOps
with ToPlusOps with ToApplicativePlusOps with ToMonadPlusOps with ToTraverseOps with ToBifunctorOps
with ToBitraverseOps with ToArrIdOps with ToComposeOps with ToCategoryOps
with ToArrowOps with ToFoldableOps with ToChoiceOps with ToSplitOps with ToZipOps with ToUnzipOps with ToMonadWriterOps with ToListenableMonadWriterOps
For example, [syntax.ToBindOps
] implicitly converts F[A]
where [F: Bind]
into BindOps[F, A]
that implements >>=
operator.
syntax.ToDataOps
introduces injected methods for data structures defined in Scalaz:
trait ToDataOps extends ToIdOps with ToTreeOps with ToWriterOps with ToValidationOps with ToReducerOps with ToKleisliOps
IdOps
methods are injected to all types, and are mostly there for convenience:
package scalaz.syntax
trait IdOps[A] extends Ops[A] {
final def ??(d: => A)(implicit ev: Null <:< A): A = ...
final def |>[B](f: A => B): B = ...
final def squared: (A, A) = ...
def left[B]: (A \/ B) = ...
def right[B]: (B \/ A) = ...
final def wrapNel: NonEmptyList[A] = ...
def matchOrZero[B: Monoid](pf: PartialFunction[A, B]): B = ...
final def doWhile(f: A => A, p: A => Boolean): A = ...
final def whileDo(f: A => A, p: A => Boolean): A = ...
def visit[F[_] : Pointed](p: PartialFunction[A, F[A]]): F[A] = ...
}
trait ToIdOps {
implicit def ToIdOps[A](a: A): IdOps[A] = new IdOps[A] {
def self: A = a
}
}
Interestingly, ToTreeOps
converts all data types to TreeOps[A]
injecting two methods:
package scalaz
package syntax
trait TreeOps[A] extends Ops[A] {
def node(subForest: Tree[A]*): Tree[A] = ...
def leaf: Tree[A] = ...
}
trait ToTreeOps {
implicit def ToTreeOps[A](a: A) = new TreeOps[A]{ def self = a }
}
So these are injected methods to create Tree
.
scala> 1.node(2.leaf)
res7: scalaz.Tree[Int] = <tree>
The same goes for WriterOps[A]
, ValidationOps[A]
, ReducerOps[A]
, and KleisliIdOps[A]
:
scala> 1.set("log1")
res8: scalaz.Writer[String,Int] = scalaz.WriterTFunctions$$anon$26@2375d245
scala> "log2".tell
res9: scalaz.Writer[String,Unit] = scalaz.WriterTFunctions$$anon$26@699289fb
scala> 1.success[String]
res11: scalaz.Validation[String,Int] = Success(1)
scala> "boom".failureNel[Int]
res12: scalaz.ValidationNEL[String,Int] = Failure(NonEmptyList(boom))
So most of the mixins under syntax.ToDataOps
introduces methods to all types to create Scalaz data structure.
Finally, we have syntax.std.ToAllStdOps
, which introduces methods and operators to Scala’s standard types.
package scalaz
package syntax
package std
trait ToAllStdOps
extends ToBooleanOps with ToOptionOps with ToOptionIdOps with ToListOps with ToStreamOps
with ToFunction2Ops with ToFunction1Ops with ToStringOps with ToTupleOps with ToMapOps with ToEitherOps
This is the fun stuff. BooleanOps
introduces shorthands for all sorts of things:
scala> false /\ true
res14: Boolean = false
scala> false \/ true
res15: Boolean = true
scala> true option "foo"
res16: Option[String] = Some(foo)
scala> (1 > 10)? "foo" | "bar"
res17: String = bar
scala> (1 > 10)?? {List("foo")}
res18: List[String] = List()
The option
operator is very useful. The ternary operator looks like a shorter notation than if-else.
OptionOps
also introduces something similar:
scala> 1.some? "foo" | "bar"
res28: String = foo
scala> 1.some | 2
res30: Int = 1
On the other hand ListOps
introduced traditional Monad related things:
scala> List(1, 2) filterM {_ => List(true, false)}
res37: List[List[Int]] = List(List(1, 2), List(1), List(2), List())
Or, I’d like to call dim sum style, where they bring in a cart load of chinese dishes and you pick what you want.
If for whatever reason if you do not wish to import the entire Scalaz._
, you can pick and choose.
Typeclass instances are broken down by the data structures. Here’s how to get all typeclass instances for Option
:
// fresh REPL
scala> import scalaz.std.option._
import scalaz.std.option._
scala> scalaz.Monad[Option].point(0)
res0: Option[Int] = Some(0)
This also brings in the “global” helper functions related to Option
. Scala standard data structures are found under scalaz.std
package.
If you just want all instances, here’s how to load them all:
scala> import scalaz.std.AllInstances._
import scalaz.std.AllInstances._
scala> scalaz.Monoid[Int]
res2: scalaz.Monoid[Int] = scalaz.std.AnyValInstances$$anon$3@784e6f7c
Because we have not injected any operators, you would have to work more with helper functions and functions under typeclass instances, which could be exactly what you want.
Typeclass syntax are broken down by the typeclass. Here’s how to get injected methods and operators for Monad
s:
scala> import scalaz.syntax.monad._
import scalaz.syntax.monad._
scala> import scalaz.std.option._
import scalaz.std.option._
scala> 0.point[Option]
res0: Option[Int] = Some(0)
As you can see, not only Monad
method was injected but also Pointed
methods got in too.
Scalaz data structure syntax like Tree
are also available under scalaz.syntax
package. Here’s how to load all syntax for both the typeclasses and Scalaz’s data structure:
scala> import scalaz.syntax.all._
import scalaz.syntax.all._
scala> 1.leaf
res0: scalaz.Tree[Int] = <tree>
Standard data structure syntax are broken down by the data structure. Here’s how to get injected methods and operators for Boolean
:
// fresh REPL
scala> import scalaz.syntax.std.boolean._
import scalaz.syntax.std.boolean._
scala> (1 > 10)? "foo" | "bar"
res0: String = bar
To load all the standard data structure syntax in:
// fresh REPL
scala> import scalaz.syntax.std.all._
import scalaz.syntax.std.all._
scala> 1.some | 2
res1: Int = 1
I thought this would be a quick thing, but it turned out to be an entire post. We’ll pick it up from here.