- About Scala
- Documentation
- Code Examples
- Software
- Scala Developers
Using generic types as lookup values
Wed, 2009-02-18, 04:59
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
Wed, 2009-02-18, 17:27
#2
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
Wed, 2009-02-18, 18:57
#3
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
Fri, 2009-02-20, 00:17
#4
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
Fri, 2009-02-20, 00:37
#5
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...
Fri, 2009-02-20, 09:07
#6
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:
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...
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: