In a sense, yes, implicits represent a global state. However, they do not change, which is a real problem with global variables - you don’t see people complaining about global constants, right? In fact, coding standards usually specify that you convert any constants in your code to constants or enumerations, which are usually global.
Note also that implicits are not in a flat namespace, which is also a common problem with global ones. They are explicitly bound to types and, therefore, to the hierarchy of packages of these types.
So, take your global variables, make them immutable and initialized on the declaration site, and put them in namespaces. Do they still look global? Do they still look problematic?
But do not stop. Implicits are type-bound, and they are as "global" as types. Does the fact that types bother you globally?
As for the use cases, there are many of them, but we can do a brief review based on their history. Originally afaik, Scala had no symptoms. What is Scala, there were types of views, a feature of many other languages. We can still see that today, when you write something like T <% Ordered[T] , it means that type T can be considered as type Ordered[T] . View types is a way to automatically select parameters in type parameters (generics).
Scala then generalized this function with implicits. Automatic drops no longer exist, and instead you have implicit conversions that are simply Function1 values and therefore can be passed as parameters. From now on, T <% Ordered[T] means that the value for the implicit conversion will be passed as a parameter. Since translation is automatic, the calling function does not need to explicitly pass a parameter, so these parameters become implicit parameters.
Note that there are two concepts - implicit conversions and implicit parameters - which are very close, but not completely overlapping.
In any case, views have become syntactic sugar for implicit conversions that are implicitly passed. They will be rewritten as follows:
def max[T <% Ordered[T]](a: T, b: T): T = if (a < b) b else a def max[T](a: T, b: T)(implicit $ev1: Function1[T, Ordered[T]]): T = if ($ev1(a) < b) b else a
Implicit parameters are just a generalization of this template, allowing you to pass any implicit parameters, not just Function1 . The actual use for them then followed, and the syntactic sugar for these uses last.
One of them is Context Bounds, which is used to implement a type type template (a template, since it is not a built-in function, but simply a way to use a language that provides similar functionality to a class like Haskell). A context constraint is used to provide an adapter that implements the functionality inherent in the class but not declared by it. It offers the benefits of inheritance and interfaces without their disadvantages. For example:
def max[T](a: T, b: T)(implicit $ev1: Ordering[T]): T = if ($ev1.lt(a, b)) b else a // latter followed by the syntactic sugar def max[T: Ordering](a: T, b: T): T = if (implicitly[Ordering[T]].lt(a, b)) b else a
You probably already used this - there’s one common precedent that people usually don’t notice. It:
new Array[Int](size)
Uses the context binding of the class manifest to enable such array initialization. We can see that in this example:
def f[T](size: Int) = new Array[T](size) // won't compile!
You can write it like this:
def f[T: ClassManifest](size: Int) = new Array[T](size)
In the standard library, the most used contextual boundaries are:
Manifest // Provides reflection on a type ClassManifest // Provides reflection on a type after erasure Ordering // Total ordering of elements Numeric // Basic arithmetic of elements CanBuildFrom // Collection creation
The last three are mainly used with collections with methods such as max , sum and map . One library that makes extensive use of context boundaries is Scalaz.
Another common application is to reduce the boiler plate during operations that must share a common parameter. For example, transactions:
def withTransaction(f: Transaction => Unit) = { val txn = new Transaction try { f(txn); txn.commit() } catch { case ex => txn.rollback(); throw ex } } withTransaction { txn => op1(data)(txn) op2(data)(txn) op3(data)(txn) }
Which is then simplified as follows:
withTransaction { implicit txn => op1(data) op2(data) op3(data) }
This pattern is used with transactional memory, and I think (but I'm not sure) that the Scala I / O library also uses it.
The third common use I can think of is to make evidence about passing types, which allows us to detect things during compilation that would otherwise lead to runtime exceptions. For example, see this definition in Option :
def flatten[B](implicit ev: A <:< Option[B]): Option[B]
This makes it possible:
scala> Option(Option(2)).flatten // compiles res0: Option[Int] = Some(2) scala> Option(2).flatten // does not compile! <console>:8: error: Cannot prove that Int <:< Option[B]. Option(2).flatten // does not compile! ^
One library that makes extensive use of this feature is Shapeless.
I don’t think that the Akka library example fits into any of these four categories, but the whole point is universal: people can use it in different ways, rather than the ways prescribed by the language developer.
If you like it when prescribed for you (like Python), then Scala is just not for you.