Ben Hutchison | 1 Feb 2010 01:34
Picon
Gravatar

Swapping notes on the "Implicit Builder" pattern

Inspired by the 2.8 collections design, Ive been doing some
experimentation with an "Implicit Builder" design pattern.

The basics of the pattern are summarized (in "bare-bones" form) in the
code below. This pattern allows abstract or generic code to
instantiate concrete subtypes of which it has no knowledge, by means
of an implicit builder parameter. Ultimately, the default concrete
types are still fixed at compile time, but control over them becomes
clustered in convenient place (eg in the Builder's companion object).

//library code

abstract class Container1[T] {
  def aTransform(implicit builder: Builder[T]): Container2[T] = builder(this)
}
class StringContainer1 extends Container1[String]
class IntContainer1 extends Container1[Int]

abstract class Container2[T]
class StringContainer2 extends Container2[String]
class IntContainer2 extends Container2[Int]

trait Builder[T] {
	def apply(c: Container1[T]): Container2[T]
}
object Builder {
  implicit val stringBuilder = new Builder[String] {def apply(c:
Container1[String]) = new StringContainer2  }
  implicit val intBuilder = new Builder[Int] {def apply(c:
Container1[Int]) = new IntContainer2  }
(Continue reading)

michid | 1 Feb 2010 11:37
Picon
Gravatar

Re: Swapping notes on the "Implicit Builder" pattern


Ben,

> - The pattern allows us to use type-parameters, which only exist at
> compile time, to direct creation of runtime objects, essentially, to
> mimic "new T()". The type information travels from the
> application/client code into generic/library code, at compile time,
> where it controls instantiation of a concrete type in a generic
> context. Thus it feels like a simple form of meta-programming.

I've written about this kind of double dispatch (i.e. first dispatch on the
generic type and a second virtual dispatch on the dynamic type) a while ago. In
my case I'm using it to overcome problems with type erasure on overloaded
methods. See http://michid.wordpress.com/2008/01/18/implicit-double-dispatch/
and http://michid.wordpress.com/2008/02/08/implicit-double-dispatch-revisited/

The usage as an implicit builder pattern did not occur to me until I saw it in
the Scala 2.8 collections design and I think it makes perfectly sense!
Interestingly enough, this usage of implicits feels a bit like declarative
programming. That is, you provide declarations (in the form of implicit builders
and abstract or generic code) and the compiler figures out at compile time
(infers!) how to use them to create the required specific instances. 

Michael

Daniel Sobral | 1 Feb 2010 15:51
Picon
Gravatar

Re: Swapping notes on the "Implicit Builder" pattern

I haven't created classes based on this pattern, but I have used it to make generic functions. I discussed this on scala-internals a while ago. See, for instance, http://article.gmane.org/gmane.comp.lang.scala.internals/2140/match=can+we+decrease+noise+level.

On Sun, Jan 31, 2010 at 10:34 PM, Ben Hutchison <brhutchison <at> gmail.com> wrote:
Inspired by the 2.8 collections design, Ive been doing some
experimentation with an "Implicit Builder" design pattern.

The basics of the pattern are summarized (in "bare-bones" form) in the
code below. This pattern allows abstract or generic code to
instantiate concrete subtypes of which it has no knowledge, by means
of an implicit builder parameter. Ultimately, the default concrete
types are still fixed at compile time, but control over them becomes
clustered in convenient place (eg in the Builder's companion object).

//library code

abstract class Container1[T] {
 def aTransform(implicit builder: Builder[T]): Container2[T] = builder(this)
}
class StringContainer1 extends Container1[String]
class IntContainer1 extends Container1[Int]

abstract class Container2[T]
class StringContainer2 extends Container2[String]
class IntContainer2 extends Container2[Int]

trait Builder[T] {
       def apply(c: Container1[T]): Container2[T]
}
object Builder {
 implicit val stringBuilder = new Builder[String] {def apply(c:
Container1[String]) = new StringContainer2  }
 implicit val intBuilder = new Builder[Int] {def apply(c:
Container1[Int]) = new IntContainer2  }
}

//application code

object Test extends Application {
 val result = new StringContainer1().aTransform
 assert(result.isInstanceOf[StringContainer2])
}

Im interested in swapping notes & pointers with others who use, like,
dislike or have previously written/blogged on this pattern. Here are
some of my thoughts, which Id appreciate input/comment upon.

- The pattern allows us to use type-parameters, which only exist at
compile time, to direct creation of runtime objects, essentially, to
mimic "new T()". The type information travels from the
application/client code into generic/library code, at compile time,
where it controls instantiation of a concrete type in a generic
context. Thus it feels like a simple form of meta-programming.

- In the basic example above, builders can vary the implementation
subtype of Container2, but its static type remains generic, ie
Container2[T]. I have successfully constructed an extension where the
content type (T in my example) has a type member specifying the
subtype of Container2 that will be constructed, and this is the static
type that builders create. This is a double-edged sword; downsides
include (a) T needs to be involved in the pattern, (b) the binding of
concrete types must be written twice, once in type-form and once in
object-form, (c) potential combinatorial explosion. Yet still a useful
option to have in the toolbox, I feel.

- I suspect designers using the pattern do not expect the implicit
builder parameter will ever be explicitly provided (ie overridden) at
runtime. Rather, that pattern is used because of its capability of
mimicking "new T()". You could almost say its a very powerful abuse of
implicit parameters. IMO it would be nicer to have some more
straightforward, direct construct, that didnt require advanced scala
knowledge to comprehend.

- The pattern can ease switching between families of concrete
implementation types easy, at /compile-time/. But might that be too
late, if for example I am distributing a library to clients in binary
form? One idea Im playing with is how to leverage the pattern to bake
multiple variations of a library, all with identical generic code, but
different concrete impls bound in.

- One problem Ive observed already is a need for multiple builders in
some more complex real-world scenarios (up to 3 so far). That gets
ugly. Ive tried to find a way of "bundling" multiple implicit builders
together into a single parameter, but without success. The problem is
that each builder needs it full type signature to work correctly.
Anyone know a way? Alternately, IDEs could hide/unhide implicit params
ala Scaladoc.


Ultimately, is it worth it, or are there better alternatives to the
implicit builder pattern, that enable generic code to instantiate
concrete types?

-Ben



--
Daniel C. Sobral

I travel to the future all the time.

Gmane