Luigi Sgro
Luigi Sgro

Reputation: 649

Statically-typed heterogeneous list builder

What I am trying to achieve is a way to:

  1. Define the list of types for a heterogeneous list
  2. From the definition above, build a statically typed list of values

Ideally I would like to type the following expression in the IDE:

val record = types.addValue("test").addValue(123).addValue(new java.util.Date())

having the type of the arguments to addValue() constrained by the IDE type inference engine.

What follows is a working implementation of the point 1: a specification of record fields types:

case class FieldType[V, T <: FieldType[_, _]](clazz: Class[V], tail: T) {
  def addValue(value: V) = FieldValue(value, tail)
}
case class FieldValue[V, T <: FieldType[_, _]](value: V, tail: T)
object emptyFieldType extends FieldType(classOf[Null], null)

and this is an example specification of a record made of String, Int, Date:

val types = FieldType(
  classOf[String], FieldType(
    classOf[Int], FieldType(
      classOf[java.util.Date], emptyFieldType
    )
  )
)

By using the addValue method in the FieldType type, the compiler recognises the argument types at any depth:

val a = types.addValue("") // here only String allowed
val b = types.tail.addValue(23) // here only Int allowed
val c = types.tail.tail.addValue(new java.util.Date()) // here only Date allowed

But....

I haven't yet found a way to implement a forwarding method on the FieldValue type, to achieve a fluent interface as in my first example:

val record = types.addValue("test").addValue(123).addValue(new java.util.Date())

This is a pseudo-code to give the idea:

case class FieldValue[V, T <: FieldType[_, _]](value: V, tail: T) {
  def addValue(x: What Here??) = tail.addValue(x) // not compiling!
}

I think it must be possible, since the type information for the next argument to addValue is contained in FieldValue via the tail member, as V. But I could not find a way to make this information available to the addValue method, to let the compiler validate the type of the argument value, and the IDE to suggest the correct type.

Upvotes: 2

Views: 172

Answers (1)

Gabriele Petronella
Gabriele Petronella

Reputation: 108169

You may want to take a look at shapeless.

Here's a trivial example demonstrating how you can statically specify the type of an heterogeneous list:

type R = String :: Int :: java.util.Date :: HNil

val record: R = "test" :: 123 :: new java.util.Date() :: HNil
// record: R = test :: 123 :: Thu Aug 14 00:21:52 CEST 2014 :: HNil

val record: R = "test" :: "foo" :: new java.util.Date() :: HNil
// error: type mismatch;
found   : shapeless.::[String,shapeless.::[String,shapeless.::[java.util.Date,shapeless.HNil]]]
required: R
  (which expands to)  shapeless.::[String,shapeless.::[Int,shapeless.::[java.util.Date,shapeless.HNil]]]
     val record: R = "test" :: "hola" :: new java.util.Date() :: HNil
                          ^

I don't know whether this already fits your needs, but in any case you should definitely check all the features of shapeless, as it provides many facilities for this kind of generic programming

Upvotes: 5

Related Questions