Skip to content

Instantly share code, notes, and snippets.

@chrisalbright
Created May 28, 2019 20:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrisalbright/6534ce807dd649d5d71609813c276dbc to your computer and use it in GitHub Desktop.
Save chrisalbright/6534ce807dd649d5d71609813c276dbc to your computer and use it in GitHub Desktop.
Exploration of Type Classes in Scala
/**
* I'm trying to learn type classes in Scala. I've got
* a decent handle on Scala itself, but the intricacies
* of the Type system are still foreign to me.
*
* I decided to start with what I thought would be a
* simple enough problem: Convert a Thing into a String
*/
object TypeClass {
/**
* So I've got my trait that establishes the behavior
* I want. Simple enough.
*/
trait AsString[-Thing] {
def asString(a: Thing): String
def apply(a: Thing): String = asString(a)
}
object AsString {
/**
* A static method to do the work
*/
def asString[Thing](a: Thing)(implicit ev: AsString[Thing]): String = ev(a)
/**
* And a syntax module to enrich all the Things
*/
implicit class Syntax[Thing](val a: Thing) extends AnyVal {
def asString(implicit ev: AsString[Thing]): String = ev(a)
}
/**
* So far so good, this makes sense. Now I'm going to
* create some instances...
*/
object Instances {
/**
* These make sense to me, but there is a lot of
* duplication...
*/
implicit val intAsString: AsString[Int] =
new AsString[Int] {
override def asString(a: Int): String = a.toString
}
implicit val booleanAsString: AsString[Boolean] =
new AsString[Boolean] {
override def asString(a: Boolean): String = a.toString
}
implicit val bigDecimalAsString: AsString[BigDecimal] =
new AsString[BigDecimal] {
override def asString(a: BigDecimal): String = a.toString
}
/**
* This is where my understanding falters...
*
* So it seems like this should work for any T, and it would
* reduce the number of instances required, but alas - my
* expectations were wrong:
*
* `asString(3L)` gets me a compile error:
*
* `Error: could not find implicit value for parameter ev: TypeClass.AsString[Long]
* asString(3l)`
*
* What am I missing here?
*/
// implicit def asString[T]: AsString[T] =
// new AsString[T] {
// override def asString(a: T): String = a.toString
// }
/**
* Additionally, I would expect this to work for both Some[T] and None,
* (of course where there is an instance of AsString[T]), but my
* understanding is again lacking:
*
* `asString(Option(7))` works beautifully
*
* - but
*
* `asString(Some(7))` gets me
* `Error: could not find implicit value for parameter ev: TypeClass.AsString[Some[Int]]
* asString(Some(7))`
*
* - and
* `asString(None)` gets me
* `Error: could not find implicit value for parameter ev: TypeClass.AsString[None.type]
* asString(None)`
*
*/
implicit def optionAsString[T](implicit ev: AsString[T]): AsString[Option[T]] =
new AsString[Option[T]] {
override def asString(a: Option[T]): String = a match {
case Some(value) => ev(value)
case None => ""
}
}
implicit val nothingAsString: AsString[None.type] =
new AsString[None.type] {
override def asString(a: None.type): String = ""
}
implicit def seqAsString[T](implicit ev: AsString[T]): AsString[Seq[T]] =
new AsString[Seq[T]] {
override def asString(a: Seq[T]): String = a.map(ev(_)).mkString(", ")
}
}
}
}
object RunTypeClass extends App {
import TypeClass.AsString
import AsString.Instances._
import AsString.Syntax
println(Some(1).asString)
println(None.asString)
println(Seq(1, 2, 3).asString)
println(List(1, 2, 3).asString)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment