Last active
February 23, 2018 04:20
-
-
Save malcolmgreaves/df040cb98a52d635fc92a4a4aad437ca to your computer and use it in GitHub Desktop.
Generic maximum function (max) with float-value-transforming typeclass (Val).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | |
| } |
Author
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
This is a generic
maxfunction 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 forVal.apply[B](), whose definition isimplicitly[Val[B]]. The whole reason why I wrote theapplymethod was to make the syntax look nicer 😄Syntax Note #2: The
def max[B: Val]thing should be read as "a function definition, calledmax, which works on any specific type (placeholder asB) subject to the constraint that there's typeclass evidenceVal[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
implicitscope. This has similiar rules -- it's delimited / controlled by{ ... }sections. However, one must very explicitly put something into that scope by prefixing its definition withimplicit. I.e. a methodimplicit def, an object definitionimplicit object, or a value assignmentimplicit val. Things can access the implicit scope by requesting by type:implicitly[Val[Int]]means "find an instance ofVal[Int]that is in the implicit scope".The
def foo[B: Val]()stuff is syntactic sugar fordef foo[B]()(implicit _something: Val[B]), which is to say that there's animplicitinput parameter to the functionfoothat has typeVal[B]. In the former, access is viaimplicitly[Val[B]]. In the latter, you can useimplicitly_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.