Question about an implicit-heavy design pattern


#1

I’ve been drawn toward a style of Scala where:

  • ≈all parameters and functions are implicit
  • semantic info that normally lives in variables’ names is put into their types
  • variable-names are basically redundant / unused, because the types now hold that info

This can cut down on three kinds of boilerplate:

  • composing things into product types (and projecting fields back out of those products)
  • explicitly passing variables to functions that take eponymous arguments
  • storing intermediate values with names that are a camel-cased version of their type

I’m interested in pointers to any tooling that supports this, prior art where it worked well or poorly, or relevant terms (what is this pattern called?).

I’m also aware of the general aversion to implicit conversions in particular, and to “too much implicit magic” in general, but nevertheless feel that there is something interesting in this direction that I want to explore. I’ve gone pretty far with it on a few projects and not regretted it.

Example

Given:

case class Foo(a: String, b: Int, c: Seq[String])

We can convert it to:

case class A(value:     String )
case class B(value:        Int )
case class C(value: Seq[String])

case class Foo(A:A, B:B, C:C)

it gets interesting when we add a variety of “common sense” implicits:

object Foo {
  implicit def derive(implicit A:A, B:B, C:C) = Foo(A, B, C)

  // this needs to be mixed-in or explicitly wildcard-imported in order for these to be in scope
  trait extras {
    implicit def unwrapA(Foo: Foo) = Foo.A; implicit def deriveA(implicit Foo: Foo) = Foo.A
    implicit def unwrapB(Foo: Foo) = Foo.B; implicit def deriveB(implicit Foo: Foo) = Foo.B
    implicit def unwrapC(Foo: Foo) = Foo.C; implicit def deriveC(implicit Foo: Foo) = Foo.C
  }
  object extras extends extras
}

import Foo.extras._
// now implicit resolution can do a variety of common-sense things, e.g. (A, B, C) ⇒ Foo, Foo ⇒ A, Foo ⇒ B, Foo ⇒ C

In particular, the derive* functions let the implicit resolver “chase arrows” recursively; here’s a fiddle that takes it a step further.

Is there a name for this pattern? Are there existing macros that can help?

Thanks in advance for any suggestions!


#2

I smell a lot of ambiguous implicits if it’s applied to real world problems :slight_smile:

Could you showcase your idea on e.g. Reverse Polish notation calculator?


#3

Depending on the application, I would worry about the overhead. Wrapping in classes isn’t free on the JVM the way it is in C++. I’m guessing this is also true in Scala.js with standard JavaScript implementations. Scala Native might be able to avoid the overhead, but it might not. This will use a fair bit of extra memory and cause the garbage collector to have to do more work if you have lots of these objects being created.


#4

Opaque types should take care of that when we get them…


#5

It could at least be interesting to see an implementation of a real problem in this style.


#6

It looks like an interesting pattern indeed, a real-world example of a problem solved with this pattern would be interested. I guess the main drawback is the fact that you need to create a unique type per parameter, which hampers reuse of types?


#7

You can reuse types very well:

case class Username(value: String)
case class Password(value: String)
case class BirthDate(value: Date)
case class Address(value: String)

case class LoginForm(u: Username, p: Password)
case class UserInfo(u: Username, b: BirthDate, a: Address)
// + all the implicits boilerplate

As you see Username type is reused in the compound types. And depending on which other things you have around, you can derive either LoginForm or UserInfo types from it. (the example is silly, but I think I made the point).

I think this approach makes you build a better data model in general, because you can use “primitive” types only to wrap them and make products only out of the wrapped types. Of course, it would be nice to make use of opaque types in this setting.

I think if you go down this road, you can achieve the same by using type-records (HLists of “labeled” types) instead of case classes with all the implicits derivation boilerplate.