Implicits for the Fearless
Some programmers are married to the imperative, side-effecting mindset. This makes them fearful of Scala’s implicit keyword (among many other high-level programming constructs and abstractions). You can read all sorts of amateurish criticism of this language construct on various websites, but I plan to show why they are a necessity to the language being useful (in the intellectually true and meaningful sense — not in a “Java” “pragmatist” sense (I know I’m treading on thin ice here, but the point is worthwhile)).
I also do not plan to address the amateurish claims specifically, since if you are like me, you simply acknowledge their existence, feel a bit sad, then get over it and move on. I mention these cases so that they can be contrasted to my plan to address a discussion I had with someone (who shall remain nameless for now and leave it up to them if they wish to reveal themselves) who put forward an intellectually constructed argument about why they can/should be eliminated. These kind of discussions are very purposeful. While my opponent’s argument was well thought-out (in contrast to the aforementioned), I still think it is wrong. Here is why.
You can do away with implicit definitions in favour of named functions with partial application. This is true, but is it useful? Furthermore, Scala is not exactly friendly to partially applying function arguments, requiring varying levels of awkwardness in certain contexts. I will assume, since I am a nice guy, that this is not the case. I will now alter the argument to what I consider to be equivalent, “Haskell can do away with type-classes”. I altered the argument simply for the sake of brevity in discussion — I will tie it back to Scala code examples at the end.
Let us assume we get rid of just one Haskell type-class, Monad. A monad is a useful abstraction, so we would certainly have to represent it (if you don’t believe this, then it is almost certain that you have reinvented monads inadvertently). We might do this as follows:
{-# LANGUAGE RankNTypes #-} data Monad' m = Monad' { unit :: (forall a. a -> m a), bind :: (forall a b. m a -> (a -> m b) -> m b) }
Then, when it comes to instances, we would write:
listMonad = Monad' (return :: a -> [a]) (>>=) maybeMonad = Monad' (return :: a -> Maybe a) (>>=)
Now when we wish to write the all-important functions over monads, we have to do the following:
sequence' monad [] = unit monad [] sequence' monad (x:xs) = let (>>>=) = bind monad in x >>>= \y -> sequence' monad xs >>>= \ys -> unit monad (y:ys) mapM' monad f as = ... etc. etc.
The proposal by my opponent in the discussion is that we now write a version of each monad function for each instance by partially applying the monad instance. This is very cumbersome and importantly, less useful.
listSequence = sequence' listMonad maybeSequence = sequence' maybeMonad listMapM = mapM' listMonad ... and so on.
This creates what would otherwise be code in the order of O(1) to turn into O(n) code! This is not good. Do we get any benefit? No, there is no benefit, other than, in the case of Scala, appeasement of ‘the irrational squad’ i.e. those fervently offended by the suggestion to divorce imperative programming. Let us just address this irrationality, even if just briefly, so we can move on.
Unlike Scala, Haskell’s side-effects are controlled, so the mind-shift is forced by the compiler (a successful form of counselling?). A Scala implicit function can perform side-effects. This would be very bad; if they were controlled, would these irrational objections be raised? The answer is no and it is quite easy to expose.
A claim to the contrary is often a case of doublethink. Java is a programming language that deliberately encourages side-effects and by implication (also, by its poor type system), makes abstraction and composition extremely difficult to the point of impossibility very early on (see Functional Java for evidence). Yet even in this language — perhaps the most contrived available — we find implicit conversions that attract no objections whatsoever!
Imagine this Java signature int toInt(char c). Such a user-defined function could indeed perform side-effects and any language feature permitting implicit use of this function is likely to attract criticism. Yet, this is one example of such a function built right-in to the Java language, which is guaranteed by the language specification to perform no side-effects. It is the fact that it is internalised by the user to perform no side-effects that makes it acceptable.
This is valid Java:
int i = c; // where c is of type char
I don’t wish to labour this point, other than to make it clear how easy it is to crush these irrationalities (it continues on in a usual uninteresting fashion).
Requiring code to blow out to O(n) from O(1) is itself not bad, but to further observe that there is no gain from it is conclusive. Interestingly, this is similar to a complaint by David MacIver (and myself — I am just less vocal about it) that Scala lacks default function argument lists in preference for overloading. This also creates unnecessary code-bloat (O(1) -> O(n)) for no benefit. (I hope I am not misrepresenting David here — I have lost the reference to his complaint).
Here is the equivalent Scala code to the above, however, Scala’s type system lacks the ability to pass the unit and bind functions — instead requiring the use of traits.
trait Unit[U[_]] { def unit[A](a: A): U[A] } trait Bind[M[_]] { def bind[A, B](ma: M[A], f: A => M[B]): M[B] } case class Monad[M[_]](u: Unit[M], b: Bind[M]) object Monad { val listMonad = Monad[List](new Unit[List] { def unit[A](a: A) = List(a) }, new Bind[List] { def bind[A, B](ma: List[A], f: A => List[B]) = ma flatMap f }) def sequence[M[_], A](m: Monad[M], as: List[M[A]]): M[List[A]] = as match { case Nil => m.u.unit(Nil) case x :: xs => m.b.bind(x, (y: A) => m.b.bind(sequence[M, A](m, xs), (ys: List[A]) => m.u.unit(y::ys))) } def listSequence[A](as: List[List[A]]) = sequence[List, A](listMonad, as) def optionSequence[A](as: List[List[A]]) = sequence[Option, A](optionMonad, as) // etc. etc. O(n)! }
As you can see, I was being very nice when I converted the argument to Haskell. The argument against such a proposal becomes even stronger in the context of Scala.
To my opponent, thanks for the discussion; it was fun and the intellectual maturity is admirable ![]()
July 5th, 2008 at 2:24 pm
To me, implicits look a lot like global variables, which is why I don’t like them.
Or maybe I missed something?
July 5th, 2008 at 2:57 pm
Importantly, implicits are not variable, nor are they global. I’m not sure what else to say
July 5th, 2008 at 3:24 pm
Hence me using “like”. They act and feel just the same to a lot of programmers. For example:
scala> def speakImplicitly (implicit greeting : String) = println(greeting)
speakImplicitly: (implicit String)Unit
scala> speakImplicitly(”Goodbye world”)
Goodbye world
scala> speakImplicitly
:6: error: no implicit argument matching parameter type String was foud.
scala> implicit val hello = “Hello world”
hello: java.lang.String = Hello world
scala> speakImplicitly
Hello world
It certainly looks like this method received its parameter from a global variable.
Clearer, now?
July 5th, 2008 at 3:43 pm
Hi Sean,
I am clear on your intended description, however, what you gave is arguably not global (”global” on its own is a misnomer), and is definitely not variable. It is not variable because you have used the Scala
valkeyword which implies lack of mutation, side-effect or “varying”. The use of the term ‘global’ requires the definition of a universe of discourse to have a meaning.Most importantly, it is the “variable” part of “global variable” that is the true problem in programming. Indeed,any variable is problematic (I am a strong supporter of the arguments put forward by Erik Meijer including and especially this one). A “global variable” typically has a wide domain and so wreaks greater havoc than a lesser-scoped variable which attracts criticism. So, unlike you who has a contention with global variables, I have a problem with any variable (note, this does not mean I never use them; I just acknowledge the far-reaching implications). However, in this case, since there are no variables, the “dislike” (I quote because I am not fond of using emotional terms to describe such artifacts) is ill-founded.
A certain type of discipline in programming completely eliminates the argument presented by cases of sloppy code. I tried to address this in the post without getting too distracted from the more interesting and deeper discussion.
July 5th, 2008 at 3:59 pm
Actually, you are doing a remarkable job at writing three entire paragraphs without addressing the concern that I’m expressing.
Back to Java, thanks for your time.
July 5th, 2008 at 4:12 pm
Sean,
I’m happy for you to go on your way with Java, but I’m not sure what else to do to address your concerns, which can be split into 2:
1) Implicits “act” like global variables (and so is problematic).
2) Implicits “feel” like global variables.
I cannot address 2) and I addressed 1) by pointing out that the premise is flawed; they do not act like global variables at all; therefore, the conclusion is not necessarily true.
Enjoy Java
July 5th, 2008 at 4:19 pm
Sean,
Maybe this will help
http://www.vector-seven.com/2008/07/05/implicits-for-the-masses/
July 6th, 2008 at 8:03 am
Just to clarify, as only the term “lesser scoped” was used, implicits have whatever scope you want them to have.
You can have them retricted to a single block if desired (for example an nested function), scala imports only apply to a specific scope, implicits are no different.
Of course you can also scope them across an entire compilable unit (like in Java) but thats still very far from a global variable.
July 6th, 2008 at 8:12 am
Thanks anon for pointing that out. I should have noted in the post that Scala import statements can appear anywhere in a source file.
October 8th, 2008 at 8:46 pm
[...] just finished reading Tony Morris’s blog post on Scala implicits and saw the following comment: To me, implicits look a lot like global variables, which is why I [...]