Skip to content

Latest commit

 

History

History
256 lines (180 loc) · 8.96 KB

README.md

File metadata and controls

256 lines (180 loc) · 8.96 KB

kittens: automatic type class derivation for Cats and generic utility functions

kittens is a Scala library which provides instances of type classes from the Cats library for arbitrary algebraic data types using shapeless-based automatic type class derivation. It also provides some utility functions related to cats.Applicative such as lift, traverse and sequence to HList, Record and arbitrary parameter list.

kittens image

kittens is part of the Typelevel family of projects. It is an Open Source project under the Apache License v2, hosted on github. Binary artefacts will be published to the Sonatype OSS Repository Hosting service and synced to Maven Central.

It is available for Scala 2.11 and 2.12, and Scala.js.

To get started with SBT, simply add the following to your build.sbt file:

libraryDependencies += "org.typelevel" %% "kittens" % "1.2.0"

Build Status Gitter Maven Central

Instances derivations are available for the following type classes:

  • Eq
  • PartialOrder
  • Order
  • Hash
  • Functor
  • Foldable
  • Traverse
  • Show
  • Monoid and MonoidK
  • Semigroup and SemigroupK
  • Empty (defined in Alleycats)

Auto derived Examples

scala> import cats.implicits._, cats._, cats.derived._

scala> case class Cat[Food](food: Food, foods: List[Food])
defined class Cat

scala> val cat = Cat(1, List(2, 3))
cat: Cat[Int] = Cat(1,List(2, 3))

Derive Functor

scala> implicit val fc: Functor[Cat] = { 
          import auto.functor._           
          semi.functor }
FC: cats.Functor[Cat] = cats.derived.MkFunctor2$$anon$4@1c60573f

scala> cat.map(_ + 1)
res0: Cat[Int] = Cat(2,List(3, 4))

Derive Show

Note that this Show also print out field names, so its results might be more preferable than the default toString.

scala> case class Address(street: String, city: String, state: String)
scala> case class ContactInfo(phoneNumber: String, address: Address)
scala> case class People(name: String, contactInfo: ContactInfo)

scala> val mike = People("Mike", ContactInfo("202-295-3928", Address("1 Main ST", "Chicago", "IL")))


scala> //existing Show instance for Address
scala> implicit val addressShow: Show[Address] = new Show[Address] {
          def show(a: Address) = s"${a.street}, ${a.city}, ${a.state}" 
       }

scala> implicit val peopleShow: Show[People] = {
            import auto.show._
            semi.show
        } //auto derive Show for People

scala> mike.show
res0: String = People(name = Mike, contactInfo = ContactInfo(phoneNumber = 202-295-3928, address = 1 Main ST, Chicago, IL))

Note that in this example, the derivation auto derived all referenced class but still respect the existing instance in scope. For fully auto derivation please see the three modes of derivation below.

Sequence examples

Note that to run these examples you need partial unification enabled on scalac. For Scala 2.11.9 or later you should add the following to your build.sbt:

scalacOptions += "-Ypartial-unification"
scala> import cats.implicits._, cats.sequence._
import cats.implicits._
import cats.sequence._

scala> val f1 = (_: String).length
f1: String => Int = <function1>

scala> val f2 = (_: String).reverse
f2: String => String = <function1>

scala> val f3 = (_: String).toFloat
f3: String => Double = <function1>

scala> val f = sequence(f1, f2, f3)
f: String => shapeless.::[Int,shapeless.::[String,shapeless.::[Float,shapeless.HNil]]] = <function1>

scala> f("42.0")
res0: shapeless.::[Int,shapeless.::[String,shapeless.::[Float,shapeless.HNil]]] = 4 :: 0.24 :: 42.0 :: HNil

//or generic over ADTs
scala>  case class MyCase(a: Int, b: String, c: Float)
defined class MyCase

scala>  val myGen = sequenceGeneric[MyCase]
myGen: cats.sequence.sequenceGen[MyCase] = cats.sequence.SequenceOps$sequenceGen@63ae3243

scala> val f = myGen(a = f1, b = f2, c = f3)
f: String => MyCase = <function1>

scala> f("42.0")
res1: MyCase = MyCase(4,0.24,42.0)

Traverse works similarly but you need a Poly.

Lift examples

scala> import cats._, implicits._, lift._
import cats._
import implicits._
import lift._

scala> def foo(x: Int, y: String, z: Float) = s"$x - $y - $z"

scala> val lifted = Applicative[Option].liftA(foo _)
lifted: (Option[Int], Option[String], Option[Float]) => Option[String] = <function3>

scala> lifted(Some(1), Some("a"), Some(3.2f))
res0: Option[String] = Some(1 - a - 3.2)

Three Modes of Derivation

Kittens provides three objects for derivation cats.derived.auto, cats.derived.cached and cats.derived.semi The recommended best practice is going to be a semi auto one:

import cats.derived

implicit val showFoo: Show[Foo] = {
   import derived.auto.show._
   derived.semi.show
}

This will respect all existing instances even if the field is a type constructor. For example Show[List[A]] will use the native Show instance for List and derived instance for A. And it manually caches the result to the val showFoo. Downside user will need to write one for every type they directly need a Show instance

There are 3 alternatives:

  1. full auto:
import derived.auto.show._

The downside is that it will re-derive for every use site, which multiples the compilation time cost.

  1. full auto cached
import derived.cached.show._

Use this one with caution. It caches the derived instance globally. So it's only applicable if the instance is global in the application. This could be problematic for libraries, which has no control over the uniqueness of an instance on use site. It relies on shapeless.Cached which is buggy. Mile Sabin is working on a language level mechanism for instance sharing.

  1. manual semi
implicit val showFoo: Show[Foo] =  derived.semi.show

It has the same downside as the recommenced semi-auto practice but also suffers from the type constructor field issue. I.e. if a field type is a type constructor whose native instance relies on the instance of the parameter type, this approach will by default derive an instance for the type constructor one. To overcome this user have to first derive the instance for type parameter. e.g. given

case class Foo(bars: List[Bar])
case class Bar(a: String)

Since the bars field of Foo is a List of Bar which breaks the chains of auto derivation, you will need to derive Bar first and then Foo

implicit val showBar: Show[Bar] =  semi.show
implicit val showFoo: Show[Foo] =  semi.show

This way the native instance for Show[List] would be used.

kittens and Typelevel Scala

Typelevel Scala provides a partial fix for SI-7046 which can present obstacles to the uses of shapeless's Generic and LabelledGeneric for the sealed trait at the root of an ADT such as you find in Kittens. If it appears that these two type classes are unable to find (all of) the subclasses of an ADT root trait then please try using Typelevel Scala and see if it resolves the issue.

To use Typelevel Scala you should,

  • Update your project/build.properties to require SBT 0.13.13 or later,

    sbt.version=0.13.13
    
  • Add the following to your build.sbt immediately next to where you set scalaVersion,

    scalaOrganization := "org.typelevel"
    

If this does resolve the problem, please lend your support to the pull request being merged in Lightbend Scala.

Participation

The Kittens project supports the Scala code of conduct and wants all of its channels (mailing list, Gitter, github, etc.) to be welcoming environments for everyone.

Building kittens

kittens is built with SBT 0.13.9 or later, and its master branch is built with Scala 2.11.7 by default.

Contributors