-
-
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 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.
This is so beautiful I almost cried
Run this code here:
https://scastie.scala-lang.org/malcolmgreaves/R2RkMnzxTkOSp99LeYPcWQ
If you
:pastethis code into ascalaREPL, you can callexample()to see the code working.