Introduction to Shapeless !

Table of contents
Reading Time: 3 minutes

Shapeless is a type class and dependent type based generic programming library for Scala. It is an Open Source project under the Apache License v2, hosted on github.

Well, simply put, it is a well known library for generic programming in scala.

Earlier, reflection APIs were used to write generic programs. However, since reflection is usually done in runtime, it sacrifices type-safety, and introduces runtime failures.

But luckily, Shapeless is there to solve problems during compilation where they would normally be tackled in runtime. Shapeless aims to give you confidence that if a piece of code compiles, it will run as well.

Using Shapeless:

To include the Shapeless in your SBT project for scala 2.11.8 you should add the following in your SBT build,

libraryDependencies ++= Seq(
  "com.chuusai" %% "shapeless" % "2.3.1"
)

Shapeless has a wide range of features. Lets have a look at two of them where Scala did not join hands with us.

  • Polymorphic function values

    Before that, lets understand what a monomorphic and a polymorphic function is:

    Monomorphic methods can only be applied to arguments of the fixed types specified in their signatures and their subtypes. For eg.

    def findSize(s: String): Int = s.length

    However, polymorphic methods can be applied to arguments of any types which correspond to acceptable choices for their type parameters. For eg.

    def findSize[T](l: List[T]): Int = l.length

    Scala allows both monomorphic as well as the polymorphic methods. The real problem arises with function values.

    In Scala, we cannot achieve polymorphic function values and therefore it produces some lack of expressiveness. Try assigning function to a val(Eta expansion):

    scala> def monomorphicListToSet(l: List[Int]): Set[Int]= l.toSet
    monomorphicListToSet: (l: List[Int])Set[Int]
    
    scala> val a = monomorphicListToSet _
    a: List[Int] => Set[Int] = <function1>

    Have a look at the definition of the above function that transforms the List[Int] into a Set[Int]. The type Int is restricted here. But Scala syntax doesn’t allow to define something similar for polymorphic functions

    scala> def polymorphicListToSet[T](l: List[T]): Set[T] = l.toSet
    
    polymorphicListToSet: [T](l: List[T])Set[T]
    
    scala> val sadlyMonomorphic = polymorphicListToSet _
    
    sadlyMonomorphic: List[Nothing] => Set[Nothing] = <function1>

    this is where the compiler goes wrong, it says the list contained type is Nothing.

    Here Shapeless comes to our rescue.

    It provides an encoding of polymorphic function values. It supports natural transformations, First of them has the following notation:

    import shapeless.poly._ scala> val polyOptionToList = new (Option ~> List){
         | def apply[T](f: Option[T]): List[T]= 
         | f.toList
         | }
    polyOptionToList: shapeless.poly.~>[Option,List] = $anon$1@1e731f50
     scala> val result = 
    polyOptionToList(Option(2)) result: List[Int] = List(2)

    The other possible notation consists of defining the function behavior based on cases, where in we can define the function only for Int, String and Boolean by adding a case for each data type or as the need arises.

    import shapeless.Poly1
    scala>object size extends Poly1 {
         |   implicit def caseInt = at[Int](x ⇒ 1)
         |   implicit def caseString = at[String](_.length)
         |   implicit def caseTuple[T, U](implicit st: Case.Aux[T, Int], su: Case.Aux[U, Int]) =
         |     at[(T, U)](t ⇒ size(t._1) + size(t._2))
         | }
    defined object size
    
    scala> size(23)
    res0: Int = 1
    scala> size("foobar")
    res1: Int = 6
    scala> size((23, "foobar"))
    res2: Int = 7
    scala> size(((23, "foobar"), 13))
    res3: Int = 8

    Another promising feature of Shapeless is the support for HLists.

  • HLists (short for Heterogeneous Lists) are lists of objects of arbitrary types, where the type information for each object is kept. In fact, in Scala, we may do:

    scala>val list = 10 :: "anyString" :: 1.0 :: Nil list: List[Any] = List(10, anyString, 1.0)

    as you can see the type of list is List[Any], beacuse the common supertype of all the elements is Any. However an HList is declared in the same way:

scala>val hList = 10 :: "anyString" :: 1.0 :: HNil hList: shapeless.::[Int,shapeless.::[String,shapeless.:[Double,shapeless.HNil]]] = 10 :: anyString :: 1.0 :: HNil

except for the terminator HNil. But the type of hList is actually Int::String::Double::HNil

An HList stores the type of every element in the list. This way we know the type of the first element, the type of the second element, and so forth. As you can see, no type information is lost.

Well you might wonder, why Hlists over Lists ??

lets have an example to prove it.

scala> list.tail.head.toUpperCase 

// error: value toUpperCase is not a member of Any

scala> hList.tail.head.toUpperCase

res6: String = ANYSTRING

Why should you really use HLists?

  • HLists can be used in all conditions where a tuple would work, but without the 22- element limitation.
  • Also the above two are convertible to each other via ‘tupled’ and ‘productElements’ methods.

For other promising features of Shapeless, you can have a look at the below link:

https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0

Happy Reading 🙂

 

 

Written by 

Rachel Jones is a Solutions Lead at Knoldus Inc. having more than 22 years of experience. Rachel likes to delve deeper into the field of AI(Artificial Intelligence) and deep learning. She loves challenges and motivating people, also loves to read novels by Dan Brown. Rachel has problem solving, management and leadership skills moreover, she is familiar with programming languages such as Java, Scala, C++ & Html.

6 thoughts on “Introduction to Shapeless !4 min read

Comments are closed.

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading