22

See: Can I specify a meaningful name for an anonymous class in C#?

In C# you can write:

var e = new { ID = 5, Name= "Prashant" };
assertEquals( 5, e.ID )

But in Scala I end up writing:

var e = (5, "Prashant")
assertEquals( 5, e._1 )

Scala maintains type safety through the use of generics (as does C#), but loses the readability of the name of each field, e.g I use "_1" instead of "ID".

Is there anything like this in Scala?

Community
  • 1
  • 1
waterlooalex
  • 13,642
  • 16
  • 78
  • 99

5 Answers5

23
object T {
  def main(args: Array[String]) {  
    val e = new { var id = 5; var name = "Prashant" }
    assert(e.id == 5)
  }
}

Ok, let's make stuff clear. This does use reflection on Scala 2.7 and Scala 2.8, because the type of e is, in this case, a structural type, which Scala handles through reflection. Here is the generated code, at clean-up time (scalac -Xprint:cleanup):

package <empty> {
  final class T extends java.lang.Object with ScalaObject {
    private <synthetic> <static> var reflMethod$Cache1: java.lang.reflect.Method = null;
    private <synthetic> <static> var reflClass$Cache1: java.lang.Class = null;
    <synthetic> <static> def reflMethod$Method1(x$1: java.lang.Class): java.lang.reflect.Method = {
      if (T.this.reflMethod$Cache1.eq(null).||(T.this.reflClass$Cache1.ne(x$1)))
        {
          T.this.reflMethod$Cache1 = x$1.getMethod("id", Array[java.lang.Class]{});
          T.this.reflClass$Cache1 = x$1;
          ()
        };
      T.this.reflMethod$Cache1
    };
    @remote def $tag(): Int = scala.ScalaObject$class.$tag(T.this);
    def main(args: Array[java.lang.String]): Unit = {
      val e: java.lang.Object = {
        new T$$anon$1()
      };
      scala.this.Predef.assert(scala.Int.unbox({
        var exceptionResult1: java.lang.Object = _;
        try {
          exceptionResult1 = T.reflMethod$Method1(e.getClass()).invoke(e, Array[java.lang.Object]{})
        } catch {
          case ($1$ @ (_: java.lang.reflect.InvocationTargetException)) => {
            exceptionResult1 = throw $1$.getCause()
          }
        };
        exceptionResult1
      }.$asInstanceOf[java.lang.Integer]()).==(5))
    };
    def this(): object T = {
      T.super.this();
      ()
    }
  };
  final class T$$anon$1 extends java.lang.Object {
    private[this] var id: Int = _;
    <accessor> def id(): Int = T$$anon$1.this.id;
    <accessor> def id_=(x$1: Int): Unit = T$$anon$1.this.id = x$1;
    private[this] var name: java.lang.String = _;
    <accessor> def name(): java.lang.String = T$$anon$1.this.name;
    <accessor> def name_=(x$1: java.lang.String): Unit = T$$anon$1.this.name = x$1;
    def this(): T$$anon$1 = {
      T$$anon$1.this.id = 5;
      T$$anon$1.this.name = "Prashant";
      T$$anon$1.super.this();
      ()
    }
  }
}

There is some caching going on, but if I alternated between id and name it would invalidate the cache already. Scala 2.8 also does reflection, and also caches, but it uses a more efficient caching technique, which should provide better overall performance. For reference, here is the clean-up of Scala 2.8:

package <empty> {
  final class T extends java.lang.Object with ScalaObject {
    final private <synthetic> <static> var reflParams$Cache1: Array[java.lang.Class] = Array[java.lang.Class]{};
    @volatile
    private <synthetic> <static> var reflPoly$Cache1: scala.runtime.MethodCache = new scala.runtime.EmptyMethodCache();
    <synthetic> <static> def reflMethod$Method1(x$1: java.lang.Class): java.lang.reflect.Method = {
      var method1: java.lang.reflect.Method = T.reflPoly$Cache1.find(x$1);
      if (method1.ne(null))
        return method1
      else
        {
          method1 = x$1.getMethod("id", T.reflParams$Cache1);
          T.reflPoly$Cache1 = T.reflPoly$Cache1.add(x$1, method1);
          return method1
        }
    };
    def main(args: Array[java.lang.String]): Unit = {
      val e: java.lang.Object = {
        new T$$anon$1()
      };
      scala.this.Predef.assert(scala.Int.unbox({
        val qual1: java.lang.Object = e;
        {
          var exceptionResult1: java.lang.Object = _;
          try {
            exceptionResult1 = T.reflMethod$Method1(qual1.getClass()).invoke(qual1, Array[java.lang.Object]{})
          } catch {
            case ($1$ @ (_: java.lang.reflect.InvocationTargetException)) => {
              exceptionResult1 = throw $1$.getCause()
            }
          };
          exceptionResult1
        }.$asInstanceOf[java.lang.Integer]()
      }).==(5))
    };
    def this(): object T = {
      T.reflParams$Cache1 = Array[java.lang.Class]{};
      T.reflPoly$Cache1 = new scala.runtime.EmptyMethodCache();
      T.super.this();
      ()
    }
  };
  final class T$$anon$1 extends java.lang.Object {
    private[this] var id: Int = _;
    <accessor> def id(): Int = T$$anon$1.this.id;
    <accessor> def id_=(x$1: Int): Unit = T$$anon$1.this.id = x$1;
    private[this] var name: java.lang.String = _;
    <accessor> def name(): java.lang.String = T$$anon$1.this.name;
    <accessor> def name_=(x$1: java.lang.String): Unit = T$$anon$1.this.name = x$1;
    def this(): T$$anon$1 = {
      T$$anon$1.super.this();
      T$$anon$1.this.id = 5;
      T$$anon$1.this.name = "Prashant";
      ()
    }
  }
}
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • Clever. Does this create a new anonymous AnyRef subclass with those id and name members? – joev Dec 01 '09 at 17:57
  • Yes, it does. It won't perform well with Scala 2.7, IIRC, because it would use reflection for it. I'm not sure, but if it matters, you should check it out. – Daniel C. Sobral Dec 01 '09 at 18:12
  • I don't know why that would be so. The compiler has access to the (anonymous) class definition, so there should be no need for reflection. Only structural typing entails use of reflection, as far as I know. – Randall Schulz Dec 01 '09 at 18:22
  • Interesting.. when would reflection get used? Will 2.8 avoid the need for reflection? – waterlooalex Dec 01 '09 at 18:22
  • Thanks for all the detail, looks interesting. It'd be great one day if Scala supported this without reflection like C# does. – waterlooalex Dec 01 '09 at 18:54
  • But can you guarantee that C# doesn't do the same thing? :-) – Daniel C. Sobral Dec 01 '09 at 19:44
  • No, I can't, we'd have to ask someone from MS. But I am 99% sure no reflection is involved. For example, use of anonymous classes is type-safe and checked at compile time, and known in the IDE, so it seems unlikely reflection (a runtime feature) would be needed. – waterlooalex Dec 01 '09 at 20:06
  • Looks to me like C# just generates a regular (but anonymous) class for you. http://www.codeguru.com/csharp/csharp/cs_misc/designtechniques/article.php/c11551__2/ – waterlooalex Dec 01 '09 at 20:19
  • 1
    In Scala, anonymous classes are type-safe and checked at compile time. The language designers here made a decision not to pollute things by creating a new thunk with a named class for each type of object that you could pass when using structural types as arguments. I don't know how long they'll keep doing this -- I'd imagine a major under-the-hood refactoring the Scala compiler when JDK 7 releases with several new VM features speed this kind of thing up, and when they see how Java 7 handles closures. – Ken Bloom Dec 02 '09 at 02:36
  • Just to clarify further -- the reflection is "under-the-hood" stuff for how they Scala implements this stuff. These types are still perfectly type safe and checked by the compiler. (Maybe the reflection helps with Java interoperability.) – Ken Bloom Dec 02 '09 at 02:38
  • Still works also in Scala 2.11.5... it creates an AnyRef. I hope no reflection takes place under the scenes in 2.11... – matanster Feb 10 '15 at 23:13
  • @matt I expect it does. Just follow the instructions I gave to check it for yourself -- I haven't tried. – Daniel C. Sobral Feb 13 '15 at 02:39
20

You can also name the parts of the tuple you're assigning to, as in:

val (ID, Name) = (5, "Prashant")
assertEquals( 5, ID )

You can also use this like:

val (ID, Name, Age) = functionThatReturnsATuple3
println("ID: " + ID + ", age: " + Age)

When I first read about the _x syntax I thought it was great and used it a lot. I've since basically stopped using it as when I have to look at code I wrote two months ago I have to spend a load of time trying to work out what the types of the _1, _2 etc. are. I suppose it's obvious in hindsight that id is much more readable than pair._1.

This can also be used inside functions like map, filter etc. like:

val list: List[ (Int, String, Double) ] = ...
list map { case (id, name, time) => ... }
list filter { case (_, name, _) => name == "X" }

Note that in the filter you can use _s for elements which you aren't going to use in the body of the function. This can be useful when skimming over code like that to establish what parts of structures are being used and how values are built up.

ams
  • 556
  • 2
  • 9
  • That looks interesting - but can you use it for projection, e.g. mapping a list of items to named tuples? – waterlooalex Dec 03 '09 at 14:26
  • I'm not quite sure what you mean, you can do: val list: List[ (Int, String, Double) ] = ...; list map { case (id, name, time) => ... } – ams Dec 03 '09 at 15:41
  • In C# you could do: employees.map( e => new { FirstName = e.Name.First, LastName = e.Name.Last } ).toList for example, which 'projects' the list of employees into a list of anonymous name classes. So in Scala you could do: employees.map( e => ((e.Name.First, e.Name.Last)) ).toList but you lose the names of the fields in the tuple. – waterlooalex Dec 04 '09 at 13:54
  • In fact, in C# (you're probably sick of the C# comments I know) you can do employees.map( e => new { e.Name.First, e.Name.Last } ).toList which will use an anonymous class with fields with names First and Last automagically. – waterlooalex Dec 04 '09 at 13:57
  • This was very nice, but I think quite less useful when heavily using the standard collections library unless you map most collection transformation results ― into objects, which kind of defeats the purpose of being coherent and free of boilerplate. Or is it? – matanster May 21 '16 at 12:57
9

I would simply make a case class:

object Yo{
  case class E(id: Int, name: String)

  def main(){
    val e = E(5,"Prashant")
    println("name="+e.name+", id="+e.id)
  }
}

Not sure if it is as efficient as Daniel's answer but I expect it is the same (I'd appreciate comments on that). In any case, I find it more readable, using only one additional line that is shared if you have more than one instance if E. Also you could add method to the case class, such as:

case class E(id: Int, name: String){
  def hasId(id: Int) = this.id==id
}

val e = E(5,"Prashant")
assert(e hasId 5)
Community
  • 1
  • 1
Juh_
  • 14,628
  • 8
  • 59
  • 92
  • This is more readable, but gets cumbersome to code and less readable the more you get to heavy collection transformations where you generate and consume various kinds of tuples. For those cases I think anonymous objects as in one of the adjacent answers here may work best. – matanster May 21 '16 at 13:08
  • It is a matter of preferences. Note that I follow (because a I agree with) https://www.originate.com/library/scala-guide-best-practices point 7: "Do not overuse tuples". – Juh_ Sep 18 '17 at 09:42
  • I think you should be able to order a collection of named tuples, but a case class can't be automagically ordered because the fields are not ordered (whereas they are ordered in a tuple). – combinatorist Oct 30 '17 at 15:44
  • I would think that case class fields are ordered, but doesn't provide an api to access by indices. Note that, to my knowledge, it is the same for tuple, the only difference is that the filed *names* are made out of consecutive number (_1, _2, ...). But you can't access a tuple item using an Int, such as `myTuple(2)` (at least without using some shapeless dark-magic :-) – Juh_ Oct 30 '17 at 16:19
4

As Juh_ suggests, extending a case class should do what you want:

scala> case class E(id: Int, name: String)
defined class E

scala> val e = new E(5, "Prashant")
e: E = E(5,Prashant)

scala> e.name
res3: String = Prashant

Case classes provide an equals method, and they also extend the Product trait, which is the same trait that Scala tuples extend. Maybe in the future they will also extend the ProductN traits.

If you use anonymous classes as suggested in some other answers, you don't end up with real tuples! For example, you don't get the equals method:

scala> val x = new { val count = 5 }
x: AnyRef{val count: Int} = $anon$1@29ca901e

scala> val y = new { val count = 5 }
y: AnyRef{val count: Int} = $anon$1@1dfe2924

scala> x == y
res4: Boolean = false

As of Scala 2.11 extending Tuple2 does work, but this is deprecated because you're not supposed to extend case classes.

You could also extend the Product2 trait, but that doesn't provide implementations of any methods, so you'd have to write all the methods yourself.

You could probably also use a Shapeless HList, which would give you lots of fancy features at the cost of adding an external dependency.

I also tried Twitter's jaqen library, but that didn't compile for me in Scala 2.11.

I'm currently using Scala 2.11, so I can't guarantee that this advice applies to other versions of Scala.

Community
  • 1
  • 1
paulkernfeld
  • 2,171
  • 1
  • 16
  • 16
1

Scala 2.8 has improved the type system to the point that it is possible to have statically and heterogeneously typed arrays and lists, so presumably the same could be done with maps. Check out Jesper Nordenberg's blog on "Type Lists and Heterogeneously Typed Arrays" for his implementation.

Nathan Kleyn
  • 5,103
  • 3
  • 32
  • 49
Randall Schulz
  • 26,420
  • 4
  • 61
  • 81
  • 1
    Can you elaborate? Looking at Jesper's post it looks like its just another way to implement Tuples in scala, but without having to have Tuple1, Tuple2, Tuple3, etc. I'm not yet seeing how it could address the name of the fields within the tuple. – waterlooalex Dec 01 '09 at 17:51
  • 1
    "... presumably the same could be done with maps." Left as an exercise for the reader, of course. Anyway, Daniel's solution is preferable if the fields are fixed at the time the instance is created. – Randall Schulz Dec 01 '09 at 18:08
  • 1
    By "done with maps", do you mean have a map where each item is of a different type, and the field names are used as keys? – waterlooalex Dec 01 '09 at 18:56
  • 1
    That's what I was thinking, but I didn't think about it much. – Randall Schulz Dec 01 '09 at 21:25