Skip to content

Instantly share code, notes, and snippets.

@sortega
Last active August 22, 2019 12:40
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 sortega/81d7a11ddc19afe0ac25c5de0981a7d7 to your computer and use it in GitHub Desktop.
Save sortega/81d7a11ddc19afe0ac25c5de0981a7d7 to your computer and use it in GitHub Desktop.
Typeclasses sample code
package fp.typeclasses
import org.apache.commons.text.StringEscapeUtils
sealed abstract class Json
object Json {
type JField = (String, Json)
case object JNull extends Json {
override def toString: String = "null"
}
final case class JString(s: String) extends Json {
override def toString: String = '"' + StringEscapeUtils.escapeJava(s) + '"'
}
final case class JNumber(value: Double) extends Json {
override def toString: String = value.toString
}
final case class JBool(value: Boolean) extends Json {
override def toString: String = value.toString
}
final case class JObject(fields: List[JField]) extends Json {
override def toString: String =
fields
.collect {
case (key, value) if value != JNull =>
""""%s":%s""".format(StringEscapeUtils.escapeJava(key), value.toString)
}
.mkString("{", ",", "}")
}
final case class JArray(elems: List[Json]) extends Json {
override def toString: String = elems.mkString("[", ",", "]")
}
def obj(fields: (String, Json)*): JObject = JObject(fields.toList)
def arr(elems: Json*): JArray = JArray(elems.toList)
}
package fp.typeclasses
trait Jsonable[A] {
def toJson(a: A): Json
}
object Jsonable {
implicit val stringJsonable: Jsonable[String] = (s: String) => Json.JString(s)
implicit val intJsonable: Jsonable[Int] = i => Json.JNumber(i.toDouble)
implicit def listJsonable[A](implicit J: Jsonable[A]): Jsonable[List[A]] =
elems => Json.JArray(elems.map(_.toJson))
implicit def optionJsonable[A: Jsonable]: Jsonable[Option[A]] = {
case None => Json.JNull
case Some(a) => a.toJson
}
implicit def mapJsonable[A: Jsonable]: Jsonable[Map[String, A]] =
map => Json.JObject(map.mapValues(_.toJson).toList)
}
package fp.typeclasses
final class JsonOps[A](val a: A) extends AnyVal {
def toJson(implicit J: Jsonable[A]): Json = J.toJson(a)
}
package fp
package object typeclasses {
implicit def toJsonOps[A](a: A): JsonOps[A] = new JsonOps[A](a)
}
package fp.typeclasses
import scalaz.Scalaz._
object TypeclassesMain extends App {
// 1. Jsonable[A]
println("hello\nworld".toJson)
println(List(1, 23, 48).toJson)
println(List(EntityId("chat_message", "1"), EntityId("search", "XJ3")).toJson)
// println(List("hello", EntityId("search", "XJ3")).toJson)
println(some(3).toJson)
println(none[Int].toJson)
println(
Map(
"foo" -> Some(List(1, 2)),
"bar" -> Some(List(3)),
"buzz" -> None
).toJson
)
// 2. Typeclasses in the standard library: Num, Order
println(List(1, 7, 3).sorted)
println(List(EntityId("search", "XJ3"), EntityId("chat_message", "1")).sorted)
def sumOld[N](nums: List[N])(implicit N: Numeric[N]): N = nums.foldLeft(N.zero)(N.plus)
def sum[N](nums: List[N])(implicit N: Numeric[N]): N = {
import N._
nums.foldLeft(zero)(_ + _)
}
println(sum(List(1, 2, 3)))
println(sum(List(1d, 2d, 3d)))
println(sum(List(BigDecimal(0.3), BigDecimal(0.4))))
}
@bluedigits
Copy link

bluedigits commented Aug 22, 2019

Great, thanks! Instead of having package.scala you also could define a Syntax object like this:

object JsonableSyntax {
  implicit class JsonableOps[A](a: A) {
    def toJson(implicit jsonable: Jsonable[A]): Json = {
      jsonable.toJson(a)
    }
  }
}

right?

@sortega
Copy link
Author

sortega commented Aug 22, 2019

And then import it explicitly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment