« Return to Thread: Structural types and generics

Re: Structural types and generics

by Viktor Klang :: Rate this Message:

Reply to Author | View in Thread

Why not try

class StringTemplate(val varRegex : String, val resolver : (String) => Option[String])

On Mon, Jul 6, 2009 at 11:32 PM, Brian Clapper <bmc@...> wrote:

I'm wondering if there's a cleaner solution to this problem. I have a class
called StringTemplate (conceptually similar to Python's StringTemplate
class). It's instantied with

- a regex that defines the syntax of a variable reference
- a "resolver" object that can take a variable name and return a value

For maximum flexibility, I chose to model the resolver with a structural
type, like so:

   class StringTemplate(val varRegex: Regex, val resolver: {def get(s:
String): Option[String]})

There's a method in the code that resolves a variable reference; it's
pretty simple:

   private def getVar(name: String): String = {
       resolver.get(name) match {
           case None        => ""
           case Some(value) => value.toString
       }
   }

Everything compiles just fine.

If I pass a Map[String,String] as the resolver, however, the getVar()
method fails at runtime, because Scala generates code that attempts to find
the "get" method via reflection. Of course, due to type erasure, the Map
classes don't actually have a method with the signature

      get(String)

so the lookup fails.

I solved the problem with a kludge:

   class StringTemplate(val varRegex: Regex, val resolver: {def get(s:
String): Option[String]}) {
       // Kludge: Have to cast the resolver to an Any/Any type, because
       // structural types work via reflection, and reflection has to work
       // with type erasure. If a Map[String,String] is passed as the
       // resolver, Scala will fail to find a "get(String)" method, because,
       // due to erasure, the REAL method is "get(Any)". Hence this kludged
       // cast. (At least it's hidden from the caller.)

       private type ResolverType = {def get(s: Any): Option[Any]}
       private val varResolver   = resolver.asInstanceOf[ResolverType]

Then, I changed my getVar() method to use the type-cast varResolver
variable, instead, and all is fine.

This solution works fine, and the ugliness is hidden within my class; the
callers pass in exactly what they expect, never mind the machinations I
have to do under the covers. However, with the type cast, it'll now fail on
non-generic resolver classes (i.e., those with a get() method that has an
explicit String parameter, rather than one that's inferred from a generic
type).

I can certainly add another kludge to fix that, one that tries the first
"correct" resolver, traps the runtime reflection exception, and tries the
typecast resolver in the catch clause.

I'm just wondering if there's a less... kludgy solution to this problem
that (a) I'm just not finding via Google and (b) am just too dense to
figure out on my own.
--
-Brian

Brian Clapper, http://www.clapper.org/bmc/
"It was hell," recalls former child.
       -- caption to a B. Kliban cartoon



--
Viktor Klang
Scala Loudmouth

 « Return to Thread: Structural types and generics