When we have something like
What exactly is happening when I call
toJson(j)? I see inside the definition of
toJson it has an
implicit e. I’m not sure how the
e.json(t) is resolved, what
e represents, and how it all works with the earlier trait declaration to actually create the
TL;DR: In Haskell, the plumbing is done automatically for you, in Scala you write it yourself. (See the near 1-1 correspondence between the Haskell/Scala code.)
The implicit says that “the type
T is json-able, but the definition of serialization is separate from the definition of
So, when you want to serialize a value, you first have to find the function that implements that operation for the type, which is what the implicit defs handle. Remember a trait will be implemented by some concrete object, and this thing has to be looked up/called. Behind the scenes, the companion object is passed an object that implements the trait and it calls the json method.
Imo, the problem with Scala typeclasses is that it exposes too much implementation detail.
It might be clearer in the equivalent Haskell, where this idea came from:
data JVal = ... -- similar to trait Json[T] class Json t where json :: t -> JVal -- think of this as implicit def -- Ints are serializable instance Json Int where json i = JNum i -- this says "if type a is serializable then a list of them is serializable also" instance Json a => Json [a] where json vs = JArr (map json vs)
Haskell implements this via “dictionary passing”1, i.e. imagine the typeclass instances being compiled into an object where each interface function is a method (Haskell compiles it into a record/hashmap like thing). Each instance (for a concrete type
A) is a concrete record. The compiler does this transformation:
i.e. the function just extracts the relevant implementation from the record. The “implicit record argument” for a type a is filled in automatically by the compiler based on the actual type parameter (assuming you defined an instance for it).
To translate this to the equivalent meanings in Scala, the trait (
class ...) defines the signature of an object that the complier should implicitly look up. The implicit defs (
instance ...) implement that trait and make objects visible only to the compiler, that it can plug into places that expect implicit values at compile time. The
Object Json thing is just boilerplate.