Introduction

http://labs.bench.co/blog/2014/11/10/scalaz-for-dummies


Introduction

Scalaz is one of those things that everyone is talking about, but many teams are unsure if they actually want to use it. The question is often, “should we use Scalaz?” but we think the question should actually be “how should we use Scalaz?” 

Scalaz is an exhaustive standard library extension for Scala with a strong emphasis on functional programming and type correctness. Many parts of it are strongly inspired by Haskell and the theoretical concepts of functional programming. The library is known for pushing the abstraction barrier quite far, which gives it its reputation as a complex library (which is true!), but some concepts from it are actually quite easy, instantly usable, and very handy in programming Scala.

There are many tutorials about Scalaz explaining the theoretical concepts behind it, like Monads, Functors, Applicatives, Type Classes and the like. Learning Scalazis a great resource on this, although a bit outdated sometimes. Instead of delving into these concepts, we are going to explain how we use Scalaz at Bench and describe the subset we are currently using with code examples.

Within the code examples, we assume that the whole scalaz world was imported:

import scalaz._, Scalaz._

 

Tagging

Tagging makes creating new types super easy. It uses the  @@ symbol to "tag" an existing type as another type (in other words, it creates a new type). So  String @@ Textshould be read as "String tagged with Text".

For a practical example, we may tag a String with the Id tag so that we never accidentally pass any String as an Id.

object Tags {
 sealed class Id
}
type Id = String @@ Tags.Id
def Id(s: String) = s.asInstanceOf[Id]
The  @@ is just a type (or more concretely a type alias). Symbolic types that take two parameters can be written in the infix position so  String @@ Id is the same as @@[String, Id].
So here the type alias Id is actually an alias to  String @@ Tags.Id, which expands to  @@[String, Tags.Id], which in turn expands to  String with Tagged[Id].

Tags can be used to:

  • Ensure type safety of APIs or data structures - it’s often better to use a tagged type than multiple String/Int or other primitive types
  • "Fake" a new type for type-classes (see below)

 

Typeclasses

Typeclasses are a mechanism for ad-hoc compile time polymorphism. A good blog post about type classes is Polymorphism and Typeclasses in Scala.

The important thing to note about typeclasses is that they "extend" the types at compile time. In scala this is implemented through implicits. There are two parts to a typeclass - the definition and the evidence (implementation) - similar to how in sub-type polymorphism there are interfaces and implementations.

In contrast to sub-type polymorphism:

  • Each class may have multiple implementations of type-classes, which are then imported into scope
  • We can make a typeclass out of classes that we do not have the source of (or that we do not want to touch)

 

Equal[A] typeclass

Like any other Scala shop, we use a lot of different type classes at Bench. The ones we use the most from Scalaz are Equal, Order and Enum. As an example of one of them, we’ll describe the Scalaz Equal typeclass, which provides a  === type-safe equals method (and a type-safe  =/= non-equals method).
Type safety means that  === is only defined for types that provide explicit evidence of equality. So  1 === "1" won't compile (no evidence of  Equals[Any]). This is different to the  == behavior of Scala’s Any, which at best will only give a warning during compile time.

This means that for any types that we define, we must provide our own evidence of equality.

Fortunately, this is quite easy:

case class MyCaseClass(foo: Int, bar: String)

implicit val myCaseClassHasEquals: Equal[MyCaseClass] = new Equal[A] {
   def equal(a1: MyCaseClass, a2: MyCaseClass): Boolean = a1 == a2
}

// or use a helper method that derives equal from universal equality of Any
implicit val myCaseClassHasEquals: Equal[MyCaseClass] = Equal.equalA

 

Options

Scalaz does not implement its own Option format (yet) but adds some cool methods to help you work with Scala Options. 

Scalaz adds the some and  none[A] methods defined on Any. They are used like this:
val x: Option[Int] = 1.some
val y: Option[Int] = none[Int]
These methods are nice because they always return  Option[A], which helps the type interferencer. Compare:
1.some.fold {
  none[Int]
} {
  1.some
}

with

 Some(1) fold {
     None: Option[Int]
 }
     Some(1)
 }
 
Without the  Option[Int], it would result in the following compile error:
error: type mismatch;
 found : Some[Int]
 required: Int => None.type

 

\/ (Disjunction or “the other Either”)

\/[A, B] can be understood as a nicer version of Scala’s Either - mostly because it has utility methods defined in it and you do not need to play around with projections to get things done.
\/ is right biased, which means that all operations like map, flatMap, etc. work on the "right" part of the type, which by convention is the success part.  A \/ B can also be swapped to  B \/ A by using  .swap.

Creating this is easy and most commonly you will find yourself using:

scala> 1.right[String] 
res1: scalaz.\/[String,Int] = \/-(1)

scala> "Nope".left[Int] 
res2: scalaz.\/[String,Int] = -\/("Nope")
Where  -\/ and  \/- correspond to left and right values.
A handy factory method is  fromTryCatch:
scala> \/.fromTryCatch { "1234".toInt }
res3: scalaz.\/[Throwable,Int] = \/-(1234)

This can be used as a drop in replacement for Scala’s Try.

 

Validation

Validation[A, B] is similar to  \/[A, B] but isn't a Monad so it can not flatMap. On the other hand, it can compose aggregating errors, which is useful to give detailed error descriptions.

Take a look at this motivating example:

val v1: Validation[Throwable, Int] = 1.success[Throwable]
val v2: Validation[Throwable, Int] = (new RuntimeException).failure[Int]
val v3: Validation[Throwable, Int] = Validation.fromTryCatch("c".toInt)
We can aggregate all the validations to a  ValidationNel ( Nel stands for non empty list) and use  sequenceU (defined on Traversable) to get:
val result: ValidationNel[Throwable, List[Int]] =
  List(v1, v2,v3).map(_.toValidationNel).sequenceU
If all validations in the list succeeded, this would expand to return a  Success[List[Int]] if not (as is the case here) this expands to  Failure[NonEmptyList[Throwable]]. Using fold on the result we can get either the success or the failure case.

 

Conclusion

This article showed some useful and at the same time simple parts of Scalaz – hopefully it motivates you to try it out on your own. As mentioned in the introduction, we believe that Scalaz can help you to use Scala even more effectively and is a valuable asset to improve your code quality.

Scalaz is a really comprehensive addition and we advise you to discuss which parts you want to use with your team before implementing anything. As much as Scalaz can improve your code, it can also be used to make your code almost unreadable and therefore inaccessible for the untrained.

At Bench we enjoy working with Scalaz, finding useful features, and discussing them within our team. We wish you the same fun we had in exploring Scalaz for your organization and hope this article was a good starting point for that!


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值