Skip to content

Instantly share code, notes, and snippets.

@malcolmgreaves
Last active February 23, 2018 04:20
Show Gist options
  • Save malcolmgreaves/df040cb98a52d635fc92a4a4aad437ca to your computer and use it in GitHub Desktop.
Save malcolmgreaves/df040cb98a52d635fc92a4a4aad437ca to your computer and use it in GitHub Desktop.
Generic maximum function (max) with float-value-transforming typeclass (Val).
case class Scored[T](item: T, value: Double)
trait Val[-A] {
def valueOf(a: A): Double
}
object Val {
def apply[T: Val]: Val[T] =
implicitly[Val[T]]
object Implementations {
// simple primitive type implementations
implicit object IntV extends Val[Int] { def valueOf(a: Int) = a.toDouble }
implicit object LongV extends Val[Long] { def valueOf(a: Long) = a.toDouble }
implicit object DoubleV extends Val[Double] { def valueOf(a: Double) = a }
implicit object FloatV extends Val[Float] { def valueOf(a: Float) = a.toDouble }
/** Produce a Val instance for any tuple whose left element has a Val instance in implicit scope. */
implicit def tupleLeftV[T: Val, Anything] = new Val[(T, Anything)] {
def valueOf(a: (T, Anything)) = a match {
case (v, _) => Val[T].valueOf(v)
}
}
/** Produce a Val instance for any tuple whose right element has a Val instance in implicit scope. */
implicit def tupleRightV[Anything, T: Val] = new Val[(Anything, T)] {
def valueOf(a: (Anything, T)) = a match {
case (_, v) => Val[T].valueOf(v)
}
}
implicit def scoredV[Anything]: Val[Scored[Anything]] = new Val[Scored[Anything]] {
def valueOf(a: Scored[Anything]) = a.value
}
}
}
def max[B: Val](xs: Traversable[B]): B =
if (xs.isEmpty)
throw new IllegalArgumentException("Cannot calculate maximum from no input.")
else if (xs.size == 1)
xs.head
else {
xs.slice(1, xs.size).foldLeft(xs.head) {
case (m, next) =>
if (Val[B].valueOf(next) > Val[B].valueOf(m))
next
else
m
}
}
def max_o[B: Val](xs: Traversable[B]): Option[B] =
try {
Some(max(xs))
} catch {
case _: IllegalArgumentException => None
}
def example(): Unit = {
println(max(Seq(1,2,3,4,5))(Val.Implementations.IntV)) // 5
println(max_o(Seq(1,2,3,4,5))(Val.Implementations.IntV)) // Some(5)
println(max(Seq(10.0, 40.0, -1.0, 22.0))(Val.Implementations.DoubleV)) // 40.0
println(max_o(Seq.empty[Long])(Val.Implementations.LongV)) // None
println("-----------------------------------")
import Val.Implementations._
println(max(Seq(1,2,3,4,5))) // 5
println(max_o(Seq(1,2,3,4,5))) // Some(5)
println(max(Seq(10.0, 40.0, -1.0, 22.0))) // 40.0
println(max_o(Seq.empty[Long])) // None
println("-----------------------------------")
println(max(Seq(("hello", 10), ("world", 20)))) // ("world",20)
println(max(Seq((33, "pad thai"), (-1, "universe")))) // (33,"pad thai")
println(max(Seq(Scored("scala",9999.0), Scored("js", 0.0)))) // Scored("scala",9999.0)
}
@malcolmgreaves
Copy link
Author

malcolmgreaves commented Feb 22, 2018

Run this code here:
https://scastie.scala-lang.org/malcolmgreaves/R2RkMnzxTkOSp99LeYPcWQ

If you :paste this code into a scala REPL, you can call example() to see the code working.

@malcolmgreaves
Copy link
Author

malcolmgreaves commented Feb 22, 2018

This is a generic max function that uses a typeclass (Val) to convert/view/represent the thing @ runtime as a floating point value.

Syntax Note #1: The Val[B] use in the code is syntactic sugar for Val.apply[B](), whose definition is implicitly[Val[B]]. The whole reason why I wrote the apply method was to make the syntax look nicer 😄

Syntax Note #2: The def max[B: Val] thing should be read as "a function definition, called max, which works on any specific type (placeholder as B) subject to the constraint that there's typeclass evidence Val[B]".

Scala Language Note #1: There's two scopes in Scala. The first your normal {-delimited scope (aka things go out of scope after you see a } after the declaration -- this is because { ... } is a block in Scala, which will have some value and an associated type).

The second is the implicit scope. This has similiar rules -- it's delimited / controlled by { ... } sections. However, one must very explicitly put something into that scope by prefixing its definition with implicit. I.e. a method implicit def, an object definition implicit object, or a value assignment implicit val. Things can access the implicit scope by requesting by type: implicitly[Val[Int]] means "find an instance of Val[Int] that is in the implicit scope".

The def foo[B: Val]() stuff is syntactic sugar for def foo[B]()(implicit _something: Val[B]), which is to say that there's an implicitinput parameter to the function foo that has type Val[B]. In the former, access is via implicitly[Val[B]]. In the latter, you can use implicitly_or_ use it by the parameter name,_something`.

Important note on implicit scope: only one thing of a particular type may be in the implicit scope at any one time! Otherwise it will be a compile error.

@carsonkahn-external
Copy link

This is so beautiful I almost cried

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