Structural types and generics

View: New views
7 Messages — Rating Filter:   Alert me  

Structural types and generics

by Brian Clapper :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


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

Re: Structural types and generics

by Viktor Klang :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

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

Re: Structural types and generics

by Brian Clapper :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On 7/6/09 5:42 PM, Viktor Klang wrote:
> Why not try
>
> class StringTemplate(val varRegex : String, val resolver : (String) =>
> Option[String])

Sure, I can do that. In fact, using a function reference did occur to me, as
an alternative to structural typing. But I prefer passing an object rather
than a function, for this particular API. Seems cleaner somehow--though I'll
admit that's a thoroughly subjective judgment.
--
-Brian

Brian Clapper, http://www.clapper.org/bmc/
The IQ of the group is the lowest IQ of a member of the group divided
by the number of people in the group.

Re: Structural types and generics

by Randall Schulz :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Monday July 6 2009, Brian Clapper wrote:
> On 7/6/09 5:42 PM, Viktor Klang wrote:
> > Why not try
> >
> > class StringTemplate(val varRegex : String, val resolver : (String)
> > => Option[String])
>
> Sure, I can do that. In fact, using a function reference did occur to
> me, as an alternative to structural typing. But I prefer passing an
> object rather than a function,

You're aware Scala functions are instances, right? They're all instances
of classes that implement one of the FunctionN traits (where N is the
function arity; traits with values of N from 0 through 22 are defined).


> for this particular API. Seems cleaner somehow--though I'll admit
> that's a thoroughly subjective judgment.

Ignoring implementation details like FunctionN, how is an object cleaner
than a function?


Randall Schulz


Re: Structural types and generics

by Erik Engbrecht :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

A function is an object.

http://www.scala-lang.org/docu/files/api/scala/Function1.html

On Mon, Jul 6, 2009 at 5:57 PM, Brian Clapper <bmc@...> wrote:
On 7/6/09 5:42 PM, Viktor Klang wrote:
> Why not try
>
> class StringTemplate(val varRegex : String, val resolver : (String) =>
> Option[String])

Sure, I can do that. In fact, using a function reference did occur to me, as
an alternative to structural typing. But I prefer passing an object rather
than a function, for this particular API. Seems cleaner somehow--though I'll
admit that's a thoroughly subjective judgment.
--
-Brian

Brian Clapper, http://www.clapper.org/bmc/
The IQ of the group is the lowest IQ of a member of the group divided
by the number of people in the group.



--
http://erikengbrecht.blogspot.com/

Re: Structural types and generics

by Arrgh :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Erik Engbrecht wrote:
> A function is an object.
>
> http://www.scala-lang.org/docu/files/api/scala/Function1.html
And better yet, some very familiar objects are also functions! :)

||| |                ^^^^^^^^^^^^^^^^

http://www.scala-lang.org/docu/files/api/scala/collection/Map.html

-0xe1a

Re: Structural types and generics

by Brian Clapper :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On 7/6/09 6:05 PM, Randall R Schulz wrote:

> Ignoring implementation details like FunctionN, how is an object cleaner
> than a function?

It isn't, really, now that I've given it some thought. Thanks.
--
-Brian

Brian Clapper, http://www.clapper.org/bmc/
Alas, I am dying beyond my means.
        -- Oscar Wilde, as he sipped champagne on his deathbed