This page is no longer maintained — Please continue to the home page at www.scala-lang.org

Using generic types as lookup values

6 replies
Patrick Roemer
Joined: 2009-02-18,
User offline. Last seen 42 years 45 weeks ago.

Hi,

I've just tried to migrate this C# code to Scala:

http://blogs.codehaus.org/people/bamboo/archives/001760_environment_base...

The core issue is the lookup of implementation instances by a (generic)
type key in [Closed]Environment. A naive translation attempt:

trait Environment {
def provide[N](implicit m: scala.reflect.Manifest[N]): Option[N];
}

class ClosedEnvironment(val bindings: Iterable[_]) extends Environment {
def provide[N](implicit m: scala.reflect.Manifest[N]): Option[N] =
bindings.find(m.erasure.isInstance(_)).asInstanceOf[Option[N]]
}

object Environments {
var env: Option[Environment] = None;

def runWith(newEnv: Environment, code: (() => Unit)) = {
val prev = env;
env = Some(newEnv);
try {
code();
}
finally {
env = prev;
}
}
}

object My {
def instance[N](implicit manifest: scala.reflect.Manifest[N]) = {
Environments.env.map(_.provide[N](manifest)).getOrElse(None)
}
}

This seems to work for the toy example, but I'm still not convinced of
the overall correctness, and the Manifest and xyInstance stuff looks
downright ugly. I guess there is a more elegant/scalaesque to implement
this?

Best regards,
Patrick

Jorge Ortiz
Joined: 2008-12-16,
User offline. Last seen 29 weeks 3 days ago.
Re: Using generic types as lookup values
It's not safe. Specifically, you'll run into trouble if you put anything generic into your environment. Observe:

  scala> val env = new ClosedEnvironment(List("Hello, world!", List(1, 2, 3)))
  env: ClosedEnvironment = ClosedEnvironment@939d2a9

  scala> env.provide[String]
  res0: Option[String] = Some(Hello, world!)

  scala> env.provide[List[Int]]
  res1: Option[List[Int]] = Some(List(1, 2, 3))

  // So far so good

  scala> env.provide[List[String]]
  res2: Option[List[String]] = Some(List(1, 2, 3))

  // Oops! This is a ClassCastException waiting to happen.

Since type parameters are erased on the JVM, there really isn't much you can do to fix this. Scala might eventually provide workarounds for this sort of this, but AFAIK there's nothing available yet.

As long as you avoid generics you should be ok though.

You may also want to look at ScalaNLP's Annotation class (http://bugs.scalanlp.org/repositories/entry/core/src/main/scala/scalanlp/util/Annotation.scala?rev=f7acf1af3f077e5f81c119752e3d55214e9f8381), which does more or less what your Environment class does, but it's also type-safe.

  scala> Annotation()
  res32: Annotation[Any] = Annotation()

  scala> Annotation() + 3.14
  res33: Annotation[Any with Double] = Annotation((double,3.14))

  scala> Annotation() + 3.14 + "Hello, world!"
  res34: Annotation[Any with Double with java.lang.String] = Annotation((double,3.14),(class java.lang.String,Hello, world!))

Notice how the type of the Annotation object grows to add the types that it supports. This lets your code declare that it needs an Annotation with types X, Y, an Z inside. If any of those types isn't present, you'll get a compile-time error.

--j

On Tue, Feb 17, 2009 at 7:58 PM, Patrick Roemer <sangamon@netcologne.de> wrote:
Hi,

I've just tried to migrate this C# code to Scala:

http://blogs.codehaus.org/people/bamboo/archives/001760_environment_based_programming_design_pattern.html

The core issue is the lookup of implementation instances by a (generic)
type key in [Closed]Environment. A naive translation attempt:

trait Environment {
 def provide[N](implicit m: scala.reflect.Manifest[N]): Option[N];
}

class ClosedEnvironment(val bindings: Iterable[_]) extends Environment {
 def provide[N](implicit m: scala.reflect.Manifest[N]): Option[N] =
         bindings.find(m.erasure.isInstance(_)).asInstanceOf[Option[N]]
}

object Environments {
 var env: Option[Environment] = None;

 def runWith(newEnv: Environment, code: (() => Unit)) = {
   val prev = env;
   env = Some(newEnv);
   try {
     code();
   }
   finally {
     env = prev;
   }
 }
}

object My {
 def instance[N](implicit manifest: scala.reflect.Manifest[N]) = {
   Environments.env.map(_.provide[N](manifest)).getOrElse(None)
 }
}

This seems to work for the toy example, but I'm still not convinced of
the overall correctness, and the Manifest and xyInstance stuff looks
downright ugly. I guess there is a more elegant/scalaesque to implement
this?

Best regards,
Patrick


Patrick Roemer
Joined: 2009-02-18,
User offline. Last seen 42 years 45 weeks ago.
Re: Using generic types as lookup values

Responding to Jorge Ortiz:

> You may also want to look at ScalaNLP's Annotation class
> (http://bugs.scalanlp.org/repositories/entry/core/src/main/scala/scalanlp...),
> which does more or less what your Environment class does, but it's also
> type-safe.

Thanks a lot for the hint and explanation, that looks interesting. I'll
take a look and try to understand it.

However, I still wonder whether there's a more "natural" way to express
this kind of selection in Scala, without resorting to reflection
"trickery". This needn't necessarily stick to the API in the C# example,
just capture the spirit. Is there any idiom I might be missing?

Best regards,
Patrick

Jesper Nordenberg
Joined: 2008-12-27,
User offline. Last seen 42 years 45 weeks ago.
Re: Using generic types as lookup values

Jorge Ortiz wrote:
> Since type parameters are erased on the JVM, there really isn't much you
> can do to fix this. Scala might eventually provide workarounds for this
> sort of this, but AFAIK there's nothing available yet.

Actually the manifests contains information about the type parameters,
it's just that the Manifest.equals() method is defined to compare type
erased classes:

scala> import scala.reflect._
import scala.reflect._

scala> def man[T](implicit m : Manifest[T]) = m
man: [T](implicit scala.reflect.Manifest[T])scala.reflect.Manifest[T]

scala> man[Int] == man[Int]
res0: Boolean = true

scala> man[List[Int]] == man[List[Int]]
res1: Boolean = true

scala> man[List[Int]] == man[List[Double]]
res2: Boolean = true

scala> man[List[Int]].toString == man[List[Double]].toString
res3: Boolean = false

So, using Manifest.toString() as lookup key should work.

/Jesper Nordenberg

Patrick Roemer
Joined: 2009-02-18,
User offline. Last seen 42 years 45 weeks ago.
Re: Using generic types as lookup values

Responding to myself:
> Responding to Jorge Ortiz:
>
>> You may also want to look at ScalaNLP's Annotation class
>> (http://bugs.scalanlp.org/repositories/entry/core/src/main/scala/scalanlp...),
>> which does more or less what your Environment class does, but it's also
>> type-safe.
>
> Thanks a lot for the hint and explanation, that looks interesting. I'll
> take a look and try to understand it.

Shamelessly stealing this code and injecting Jesper's advice, I get

class Environment[+T] (private val map: Map[String,Any]) {
def get[U>:T](implicit m: Manifest[U]) =
map.get(m.toString).asInstanceOf[Option[U]]

def apply[U>:T]()(implicit m: Manifest[U]) = get[U].get

def +[U](x: U)(implicit m: Manifest[U]) =
new Environment[T with U](map + (m.toString -> x))
}

object Environment {
def apply() = new Environment[Any](Map.empty)

def apply[T](x:T)(implicit m: Manifest[T]) =
new Environment[T](Map.empty + (m.toString -> x))
}

class MyBase[MyEnv](var env: Environment[MyEnv]) {
def runWith(newEnv: Environment[MyEnv], code: (() => Unit)) = {
val prev = env;
env = newEnv;
try {
code();
}
finally {
env = prev;
}
}

def instance[N>:MyEnv](implicit manifest: scala.reflect.Manifest[N]) =
env[N]
}

Usage:

object My extends MyBase(Environment[MovieFinder](EmptyMovieFinder))

val env = Environment[MovieFinder](DummyMovieFinder)
My.runWith(env, () => println(My.instance[MovieFinder].findAll()))

Quite different in spirit to the original code, but definitely an
interesting variant. Thanks again.

Best regards,
Patrick

David Hall 3
Joined: 2009-02-19,
User offline. Last seen 42 years 45 weeks ago.
Re: Re: Using generic types as lookup values

On Wed, Feb 18, 2009 at 9:44 AM, Jesper Nordenberg wrote:
> Jorge Ortiz wrote:
>>
>> Since type parameters are erased on the JVM, there really isn't much you
>> can do to fix this. Scala might eventually provide workarounds for this sort
>> of this, but AFAIK there's nothing available yet.
>
> Actually the manifests contains information about the type parameters, it's
> just that the Manifest.equals() method is defined to compare type erased
> classes:
>
>
> scala> import scala.reflect._
> import scala.reflect._
>
> scala> def man[T](implicit m : Manifest[T]) = m
> man: [T](implicit scala.reflect.Manifest[T])scala.reflect.Manifest[T]
>
> scala> man[Int] == man[Int]
> res0: Boolean = true
>
> scala> man[List[Int]] == man[List[Int]]
> res1: Boolean = true
>
> scala> man[List[Int]] == man[List[Double]]
> res2: Boolean = true
>
> scala> man[List[Int]].toString == man[List[Double]].toString
> res3: Boolean = false
>
>
> So, using Manifest.toString() as lookup key should work.

Clever! I too will be stealing this for the original implementation...

Viktor Klang
Joined: 2008-12-17,
User offline. Last seen 1 year 27 weeks ago.
Re: Re: Using generic types as lookup values
Advocating theft are we?

Then I'll steal it too! :D

On Fri, Feb 20, 2009 at 12:28 AM, David Hall <dlwh@cs.stanford.edu> wrote:
On Wed, Feb 18, 2009 at 9:44 AM, Jesper Nordenberg <megagurka@yahoo.com> wrote:
> Jorge Ortiz wrote:
>>
>> Since type parameters are erased on the JVM, there really isn't much you
>> can do to fix this. Scala might eventually provide workarounds for this sort
>> of this, but AFAIK there's nothing available yet.
>
> Actually the manifests contains information about the type parameters, it's
> just that the Manifest.equals() method is defined to compare type erased
> classes:
>
>
> scala> import scala.reflect._
> import scala.reflect._
>
> scala> def man[T](implicit m : Manifest[T]) = m
> man: [T](implicit scala.reflect.Manifest[T])scala.reflect.Manifest[T]
>
> scala> man[Int] == man[Int]
> res0: Boolean = true
>
> scala> man[List[Int]] == man[List[Int]]
> res1: Boolean = true
>
> scala> man[List[Int]] == man[List[Double]]
> res2: Boolean = true
>
> scala> man[List[Int]].toString == man[List[Double]].toString
> res3: Boolean = false
>
>
> So, using Manifest.toString() as lookup key should work.

Clever! I too will be stealing this for the original implementation...

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland