-
-
Save malcolmgreaves/df040cb98a52d635fc92a4a4aad437ca to your computer and use it in GitHub Desktop.
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) | |
} |
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 implicit
input 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.
This is so beautiful I almost cried
Run this code here:
https://scastie.scala-lang.org/malcolmgreaves/R2RkMnzxTkOSp99LeYPcWQ
If you
:paste
this code into ascala
REPL, you can callexample()
to see the code working.