Skip to content

Instantly share code, notes, and snippets.

@zipcode
Created June 4, 2014 18:26
Show Gist options
  • Save zipcode/8b648b7553d51a251627 to your computer and use it in GitHub Desktop.
Save zipcode/8b648b7553d51a251627 to your computer and use it in GitHub Desktop.
How Scalaz uses implicits to augment objects.
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