Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Scala Typeclass Explained: Implement a String.read Function

DZone's Guide to

Scala Typeclass Explained: Implement a String.read Function

The Typeclass pattern in Scala can be used to implement polymorphism, which will create a flexible and extensible model that's not tied to a specific implementation or traits.

· Java Zone
Free Resource

Learn how to troubleshoot and diagnose some of the most common performance issues in Java today. Brought to you in partnership with AppDynamics.

In this short article I'd like to explain how you can use the typeclass pattern in Scala to implement adhoc polymorphism. We won't go into too much theoretical details in this article, the main thing I want to show you is how easy is really is to create a flexible extensible model without tying your domain model to specific implementation or traits. There are many resources regarding scala typeclasses available on the internet, but I couldn't find a good reference, so that's why I created this one. In this example we'll look at a very pragmatic (and naive) implementation of the read typeclass from haskell. This type class allows you to convert a string to a specific type in a generic way.

What we want to accomplish with this typeclass is the following (The complete example can be found here: https://gist.github.com/josdirksen/9051baf09003dac37386)


println(Readable[Double].read("10"))
println(Readable[Int].read("10"))
println(Readable[String].read("Well duh!"))
println(Readable[List[Char]].read("Well duh!"))
println(Readable[List[String]].read("Using:A:Separator:to:split:a:String"))
// we can also use the read function directly
println("20".read[Double]);
println("Using:A:Separator:to:split:a:String".read[List[Char]]);
println("Using:A:Separator:to:split:a:String".read[List[String]]);
println(Readable[Task].read("10|Title Text|Title Content"))
println("20|Another title Text|Another title Content".read[Task])
view raw ReadSample.scala hosted with ❤ by GitHub


With the Readable typeclass we provide a generic way to convert a String to a specific type. In the example above we used the type class to convert a String to some basic types, but also to different lists and a specific case class. The functionality for the basic types isn't really that useful, since the scala String object already provides the toDouble, toString, etc. functions. This way, however, you don't need to know the specific function to call, but you can just specify the target type you want :) It, however, gets much more interesting with the Task case class you see here. As you'll see in the rest of the code, through the use of implicits we can simply add support for this case class, without having to change the implementation of either the String class or the Task class.

To implement a typeclass we first have to define the trait that defines the functions we need to implement. In this sample, we have only one function:


/**
* The readable trait defines how objects can be converted from a string
* representation to the objects instance. For most of the standard types
* we can simply use the toType function of String.
*/
traitReadable[T] {
defread(x: String):T
}
view raw ReadSample.scala hosted with ❤ by GitHub


The read function should just transform the String to the specified type T. Now that we've defined our trait, lets look at the companion object, which contains some helper classes and simple implementations:


/**
* Companion object containing helper functions and standard implementations
*/
objectReadable {
/**
* Helper function which allows creation of Readable instances
*/
deftoReadable[T](p: String=>T):Readable[T] =newReadable[T] {
defread(x: String):T= p(x)
}
/**
* Allow for construction of standalone readables, if the ops aren't used
*/
defapply[A](implicitinstance: Readable[A]):Readable[A] = instance
// Using the toReadable creates cleaner code, we could also explicitly
// define the implicit instances:
//
// implicit object ReadableDouble extends Readable[Double] {
// def read(s: String): Double = s.toDouble
// }
// implicit object ReadableInt extends Readable[Int] {
// def read(s: String): Int = s.toInt
// }
implicitvalReadableDouble= toReadable[Double](_.toDouble)
implicitvalReadableInt= toReadable[Int](_.toInt)
implicitvalReadableLong= toReadable[Long](_.toLong)
implicitvalReadableString= toReadable[String](newString(_))
implicitvalReadableBoolean= toReadable[Boolean](_.toBoolean)
implicitvalReadableCharList= toReadable[List[Char]](_.toCharArray.toList)
implicitvalReadableStringList= toReadable[List[String]](_.split(':').toList)
}
view raw ReadSample.scala hosted with ❤ by GitHub


The code shouldn't be that hard to understand. What we do here, is we create a number of Readable implementations. It's important to note that we use the implicit parameter, so that we can pull them into scope later on. At this point we can already start using the typeclass. For that we import the implicits and use the Readable type class like this:


importReadable._
importReadable.ops._
// now we can just get an instance of a readable and call the read function
// to parse a string to a specific type.
println(Readable[Double].read("10"))
println(Readable[Int].read("10"))
println(Readable[String].read("Well duh!"))
println(Readable[List[Char]].read("Well duh!"))
println(Readable[List[String]].read("Using:A:Separator:to:split:a:String"))
view raw ReadSample.scala hosted with ❤ by GitHub


Easy right? Scala will look for an implicit that matches the specified type and convert the String to that type. This is nice, but doesn't really look that nice. We need to instantiate a Readable (even though that isn't too much code) and call read to convert the String. We can make it easier by extending the String class with a read function that does the conversion for us. For this add the following to the Readable companion object.


/**
* Extend the string object with a read function.
*/
objectops {
implicitclasspp[T](s: String) {
/**
* The type parameter should have an implcit Readable in scope. Use
* implicitly to access it and call the read function
*/
defread[T:Readable]= implicitly[Readable[T]].read(s)
}
}
view raw ReadSample.scala hosted with ❤ by GitHub


These operation define an implicit transformation which add the read function the String object. All that is left is to import the ops implicit functions and we can use the read directly:


importReadable.ops._
// we can also use the read function directly
println("20".read[Double]);
println("Using:A:Separator:to:split:a:String".read[List[Char]]);
println("Using:A:Separator:to:split:a:String".read[List[String]]);
view raw ReadSample.scala hosted with ❤ by GitHub


This same approach can also be used to create a read function for case classes:

Understand the needs and benefits around implementing the right monitoring solution for a growing containerized market. Brought to you in partnership with AppDynamics.

Topics:
scala ,design patterns

Published at DZone with permission of Jos Dirksen, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}