Created
June 4, 2014 18:26
-
-
Save zipcode/8b648b7553d51a251627 to your computer and use it in GitHub Desktop.
How Scalaz uses implicits to augment objects.
This file contains 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
import scala.language.implicitConversions | |
/* | |
So earlier this week I was reading about how scalaz implements the "enhance my library" | |
pattern. It's a pretty nifty way to add methods to existing objects in a type-safe way. | |
However, this is @zip here, so of course I'm gonna use it for something gross. | |
So, I'm a fan of Javascript, inexplicably. Javascript has a… looser… notion of types. | |
In particular, all sorts of things can be true or false! Empty lists are false, full | |
ones are true. Who wouldn't want the following capability? | |
phantomjs> [] == false | |
true | |
I attempted to re-implement the following from memory, referring back when stuck: | |
http://eed3si9n.com/learning-scalaz/a+Yes-No+typeclass.html | |
Some background: Scala has the concept of _implicits_. An implicit is an argument | |
that is automatically passed to a function by the runtime. For example: | |
implicit val e: ExecutorService = Executors.newThreadPoolExecutor(2) | |
def run(f: => Unit)(implicit es: ExecutorService) { | |
es.execute(new Runnable { def run { f }}) | |
} | |
run { Console.println("Hi!") } | |
What's going on here is that as the implicit argument hasn't been provided at | |
the call site, Scala looks for a unique impicit value of type ExecutorService, | |
and passes it in automatically for us. This is a great way to carry around | |
context without having to explicitly thread it. | |
Scala also provides a function called _implicitly_. This is parameterised by a | |
type, and its effect is to pull an implicit value of that type back into an | |
explicit context: | |
val e1 = implicitly[ExecutorService] | |
e == e1 // true | |
Incidentally, we can use this for type-checking by way of type evidence. | |
trait NewHotness[T] | |
val intIsTheNewHotness: NewHotness[Int] = new NewHotness[Int] | |
def fashion[T](v: T)(ev: NewHotness[T]) { ... do stuff ... } | |
fashion(9)(intIsTheNewHotness) | |
Nothing up my sleeves here. You wanna call fashion on your type? Okay. | |
One of the things you'll need is an object of type NewHotness[T] for | |
that type. In this case, we can do that, because intIsTheNewHotness | |
is totally a thing. But if we wanted to get into the cool club with | |
a Double, we're in trouble. We don't have an object of that type. | |
But who wants to go around checking if everything is the new hotness | |
all the time? Cool knows it's cool. Sure, we wanna gatekeep our | |
stuff, but we don't want to, like, be *overt* about it. So! | |
implicit val intReallyIsTheNewHotness: NewHotness[Int] = new Hotness[Int] | |
def superFashion[T](v: T)(implicit ev: NewHotness[T]) { ... do stuff ... } | |
superFashion(9) | |
Cool! Now we have a parametrically typed function that lets in types which | |
are the new hotness, without letting in last year's Strings. This is a | |
common enough pattern, so there's syntax for it. There's always syntax | |
in Scala. | |
def amazingFashion[T : NewHotness](v: T) { ... do stuff ... } | |
*/ | |
object Main { | |
/* | |
Anyway. We begin our adventure by defining a type for things which could, | |
if you squint, be interpreted as a Boolean value. Let's call it Booly. | |
Given an instance of Booly[Int], for example, we can call boolyInt.truthy | |
on some Int to find out if it's truthy. Nice. | |
*/ | |
trait Booly[T] { | |
val truthy: (T => Boolean) | |
} | |
/* | |
This is a little helper which lets us create Booly values for a given type. | |
For example, below you'll see Booly.specify { _ != 0 } for Booly[Int]. | |
*/ | |
object Booly { | |
def specify[T](f: T => Boolean) = new Booly[T] { | |
val truthy = f | |
} | |
} | |
/* | |
Cool! Now we can work on booly types. We can use values as though they're | |
true or false, without regard for their actual type, because we can get | |
at a Booly[T] for whatever that type is and call truthy to find out. | |
*/ | |
def somethingBooly[T: Booly](v: T): Boolean = { | |
implicitly[Booly[T]].truthy(v) | |
} | |
/* | |
Bit clunky, though, isn't it? Sure, we can pass a value that can be used | |
as a boolean as an argument in a type-safe way, but who wants to add all | |
those extra parameters or all that implicitly[] stuff? | |
The solution is to create a wrapper, which we call BoolyOps. This is | |
distinct from Booly, because we need Booly for the type T and BoolyOps | |
for instances of type T. | |
Of course, if we have BoolyOps[T] then we want to be sure there's a | |
Booly[T]. And furthermore, we want to use its methods to work on our | |
wrapped type. So, we define self (of type T) and F for our Booly. | |
Then we can provide methods with wild abandon, in terms of F and self! | |
*/ | |
trait BoolyOps[T] { | |
val self: T | |
def F: Booly[T] | |
final def truthy: Boolean = F.truthy(self) | |
final def &-&[U: Booly](that: U) = F.truthy(self) && implicitly[Booly[U]].truthy(that) | |
final def |-|[U: Booly](that: U) = F.truthy(self) || implicitly[Booly[U]].truthy(that) | |
} | |
/* | |
Nice. Now we can take any Booly value and wrap it up. Let's write another | |
helper constructor. Now we can wrap (say) 9 like this: | |
val boolyOpsWrapper: Booly[Int] = BoolyOps.specify[Int](9) | |
Handy. Now we can call boolyOpsWrapper.truthy. Still a bit clunky, though. | |
The final part of the puzzle is that _implicit_. Right under this definition | |
we pull BoolyOps._ into scope. Scala is smart enough to know that when | |
asked for 9.truthy, it needs to convert 9 into an instance of BoolyOps[Int]. | |
Nice. | |
*/ | |
object BoolyOps { | |
implicit def specify[T](v: T)(implicit ev: Booly[T]) = new BoolyOps[T] { | |
val self = v | |
def F = ev | |
} | |
} | |
import BoolyOps._ | |
/* | |
Well, strictly that wasn't the final piece of the puzzle. This is: we need | |
to create instances for things which are booly. Ints are kinda truthy | |
when they're nonzero, strings and sequences are when they're nonempty, | |
and of course Booleans are booly by definition! | |
*/ | |
implicit val intBooly: Booly[Int] = Booly.specify { _ != 0 } | |
implicit val stringBooly: Booly[String] = Booly.specify { _.length > 0 } | |
implicit val boolBooly: Booly[Boolean] = Booly.specify { x => x } | |
implicit def seqBooly[T]: Booly[Seq[T]] = Booly.specify { x => x.length > 0 } | |
def main(args: Array[String]) { | |
System.out.println(if ("s".truthy) "s truthy" else "s falsey") | |
System.out.println(if ("".truthy) "empty truthy" else "empty falsey") | |
System.out.println(if (0.truthy) "0 truthy" else "0 falsey") | |
System.out.println(if (1.truthy) "1 truthy" else "1 falsey") | |
System.out.println(if (1 &-& "hi") "1 and hi truthy" else "1 and hi not both truthy") | |
} | |
/* | |
s truthy | |
empty falsey | |
0 falsey | |
1 truthy | |
1 and hi truthy | |
*/ | |
/* Gross. I love it. */ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment