Archive for May, 2008

Even more modules in Scala

In response to Rossberg's challenge to encode the following:

  2. signature S =
  3. sig
  4. structure A : sig type t; val x : t end
  5. val f : A.t -> int
  6. end
  8. structure M =
  9. struct
  10. structure A = struct type t = int; val x = 666 end
  11. fun f x = x
  12. end
  14. structure N = M :> S

In Scala, the above becomes:

  2. type S = {
  3. val A: { type T; val x: T }
  4. def f(arg: A.T): Int
  5. }
  7. val M = new {
  8. val A = new { type T = Int; val x = 666 }
  9. def f(arg: Int) = arg
  10. }
  12. val N = M : S

And using

  1. N.f
  1. N.A.x
is even well-typed, but still encounters the same problems with evaluation I mentioned earlier. And yes, the type
  1. N.A.T
is hidden:

  2. scala> val x : Int = M.A.x
  3. x: Int = 666
  4. scala> val x : Int = N.A.x
  5. <console>:7: error: type mismatch;
  6. found : N.A.T
  7. required: Int
  8. val x : Int = N.A.x
  9. ^
  11. scala> val x : N.A.T = 42
  12. <console>:7: error: type mismatch;
  13. found : Int(42)
  14. required: N.A.T
  15. val x : N.A.T = 42
  16. ^

As for how Scala, solves the double vision problem, I would look at the work on νObj, and the forthcoming paper on Featherweight Scala.

Update: Looking more closely at the definition of "double vision" as defined by Dreyer, the Scala implementation cannot currently handle his motivating example. I will need to think about how the example works in Featherweight Scala, which to my knowledge shouldn't have a problem with at least defining the type signatures. I think the short answer is that νObj and Featherweight Scala (the non-algorithmic version) solve the problem in a vacuous fashion, as the type checking problem for them is undecidable. Scala proper possibly could resolve the problem by using a less restrictive cycle detection algorithm.


Functors in Scala

Following on my earlier entry on modules in Scala, I'll give an encoding of Standard ML style functors here. You can get a pretty close approximation by using class constructor arguments. However, I am going to cheat a little to get the closest encoding I think is possible by using the experimental support for dependent method types. You can get this by running scala or scalac with the option -Xexperimental. It works okay at least some of the time, but no one has the time at the moment to commit to getting it in shape for general consumption.

So here is my example of how the encoding works. First, the SML version:

  2. signature Eq = sig
  3. type t
  4. val eq: t -> t -> bool
  5. end
  7. signature RicherEq = sig
  8. include Eq
  9. val neq: t -> t -> bool
  10. end
  12. functor mkRicherEq(arg : Eq) :> RicherEq where type t = arg.t = struct
  13. type t = arg.t
  14. val eq = arg.eq
  15. fun neq x y = not (eq x y)
  16. end

We can transliterate this example into Scala as:

  2. type Eq = {
  3. type T
  4. def eq(x: T, y: T): Boolean
  5. }
  7. type RicherEq = {
  8. type T
  9. def eq(x: T, y: T): Boolean
  10. def neq(x: T, y: T): Boolean
  11. }
  13. def mkRicherEq(arg: Eq) : RicherEq { type T = arg.T } = new {
  14. type T = arg.T
  15. def eq(x: T, y: T) = arg.eq(x, y)
  16. def neq(x: T, y:T) = !eq(x, y)
  17. }

The only problem I discovered is that it is not possible to define RicherEq in terms of Eq as we could in SML:

  2. scala> type RicherEq = Eq { def neq(x: T, y: T): Boolean }
  3. <console>:5: error: Parameter type in structural refinement may
  4. not refer to abstract type defined outside that same refinement
  5. type RicherEq = Eq { def neq(x: T, y: T): Boolean }
  6. ^
  7. <console>:5: error: Parameter type in structural refinement may
  8. not refer to abstract type defined outside that same refinement
  9. type RicherEq = Eq { def neq(x: T, y: T): Boolean }
  10. ^

Why this restriction exists I don't know. In fact, this sort of refinement should work in the current version of Featherweight Scala, so perhaps it can be lifted eventually.

I still need to think about higher-order functors, and probably spend a few minutes researching existing proposals. I think this is probably something that cannot be easily supported in Scala if it will require allowing method invocations to appear in paths. However, off hand that only seems like it should be necessary for applicative higher-order functors, but again I definitely need to think it through.

Comments (2)

Modules in Scala

I just saw a thread on Lambda the Ultimate where I think the expressive power of Scala in comparison to Standard ML's module system was misrepresented. I don't want to go into all of the issues at the moment, but I figured out would point out that you can get the same structural typing, opaque sealing, and even the equivalent of SML's where type clause.

For example, consider the following SML signature:

  2. signature Nat = sig
  3. type t
  4. val z: t
  5. val s: t -> t
  6. end

This signature can be translated in to Scala as:

  2. type Nat = {
  3. type T
  4. val z: T
  5. def s(arg: T): T
  6. }

It is then possible to create an implementation of this type, and opaquely seal it (hiding the definition of T). In SML:

  2. structure nat :> Nat = struct
  3. type t = int
  4. val z = 0
  5. fun s n = n + 1
  6. end

In Scala:

  2. val nat : Nat = new {
  3. type T = Int
  4. val z = 0
  5. def s(arg: Int) = arg + 1
  6. }

In many cases when programming with SML modules it is necessary or convenient to give a module that reveals the definition of an abstract type. In the above example, this can be done by adding a where type clause to the first line:

  2. structure nat :> Nat where type t = int = struct
  3. ...

We can do the same thing in Scala using refinements:

  2. val nat : Nat { type T = Int } = new {
  3. ...

Great, right? Well, almost. The problem is that structural types are still a bit buggy in Scala compiler at present. So, while the above typechecks, you can't quite use it yet:

  2. scala> nat.s(nat.z)
  3. java.lang.NoSuchMethodException: $anon$1.s(java.lang.Object)
  4. at java.lang.Class.getMethod(
  5. at .reflMethod$Method1(<console>:7)
  6. at .<init>(<console>:7)
  7. at .<clinit>(<console>)
  8. at RequestResult$.<init>(<console>:3)
  9. at RequestResult$.<clinit>(<console>)
  10. at RequestResult$result(<console>)
  11. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  12. at sun.reflec...

There were some issues raised about how faithful an encoding of SML functors, and well-known extensions for higher-order functors, one can get in Scala. Indeed, off the top of my head it is not entirely clear. So I need to think more about that before I write some examples.

Comments (7)

Featherweight Scala lives

With everything else I've been having to deal with the past couple months, I have not made nearly as much progress on Featherweight Scala as I would have liked. Fortunately, in the past two weeks I have finally started to get back on track.

Probably what makes working out the design of Featherweight Scala so difficult is that I am trying my best to commit to it being a valid subset of Scala. It seems like whenever I try to simplify one part of the language, it just introduces a complication elsewhere. Still, I think so far that I have been able to ensure that it is a subset with two exceptions.

The first exception is that some programs in Featherweight Scala will diverge in cases where the Scala compiler will generate a terminating program. This is because in programs that attempt to access an uninitialized field, Scala will return null, while Featherweight Scala diverges. Since the program many never attempt to use the null value, it may simple terminate without raising a NullPointerException.

The second exception arises because type checking in Featherweight Scala's type system is most likely undecidable. Given that the design is in flux, I haven't precisely verified this, but it seems quite plausible given its expressive power is similar to languages where type checking is known to be undecidable. Because type checking is undecidable, and the Scala language implementation attempts to have its type checking phase be terminating, there should be some programs that can be shown to be well-typed in Featherweight Scala that the Scala compiler will reject.

I think the other thing I've determined is that I do not understand F-bounded quantification as well as I thought I did. At least, I find myself worried about the cases where the typing rules presently require  templates, that have not yet been verified to be well-formed, to be introduced as hypotheses while verifying their own correctness. So I need to do some additional reading to see how some other languages deal with this issue. Certainly I can put my mind at ease by making the type system more restrictive, but I do not want to be so restrictive that it is no longer reasonable to call the language Featherweight Scala.

Update: Okay, so after looking at Featherweight Generic Java and νObj again more closely, they both do essentially the same kind of thing. In FGJ, the class table is well-formed if all its classes are well-formed, but checking a class's well-formedness may require presupposing that that class tale is well-formed. In νObj, the well-formedness of recursive record types is checked under the assumption that the record's self-name has the type of the record. Certainly the same sort of things come up when verifying the well-formedness of recursive functions and recursive types, but at least there what is being assumed is at a higher level of abstraction: when checking a recursive function you assume that the function has some well-formed type, or when checking a recursive type you assume that the type has some well-formed kind.


Revisiting higher-rank impredicative polymorphism in Scala

I've been meaning to write this up for a while, but it seems like there has always been something else I really ought to be doing. So I expect this will be a bit more terse than I might like. Anyway, when I wrote about encoding higher-rank universal quantification in Scala back in March, I used a rather elaborate scheme involving the use of Scala's first-class existential quantifiers. While this was the point of the exercise, surprisingly, no one called me on the fact that if you just want higher-rank impredicative polymorphism in Scala, there is a much simpler encoding. Maybe it was obvious to everyone or no one read closely enough to think of raising the issue. So today, I'll explain the better way to encode them.

First we can define an infinite family of traits to represent n-ary universal quantification, much like Scala represents n-ary functions types:

  2. trait Univ1[Bound1,Body[_]] {
  3. def Apply[T1<:Bound1] : Body[T1]
  4. }
  6. trait Univ2[Bound1,Bound2,Body[_,_]] {
  7. def Apply[T1<:Bound1,T2<:Bound2] : Body[T1,T2]
  8. }
  10. // ... so on for N > 2

Really, the key to this encoding is the use of higher-kinded quantification to encode the binding structure of the quantifier.

Now it is possible to write some examples similar to what I gave previously, but more concisely:

  2. object Test extends Application {
  3. def id[T](x : T) = x
  5. type Id[T] = T => T
  6. val id = new Univ1[Any,Id]
  7. { def Apply[T <: Any] : Id[T] = id[T] _ }
  9. val idString = id.Apply[String]
  10. val idStringList = id.Apply[List[String]]
  12. println(idString("Foo"))
  13. println(idStringList(List("Foo", "Bar", "Baz")))
  15. type Double[T] = T => (T, T)
  16. val double = new Univ1[Any,Double]
  17. { def Apply[T <: Any] : Double[T] = (x : T) => (x, x) }
  19. val doubleString = double.Apply[String]
  20. val doubleStringList = double.Apply[List[String]]
  22. println(doubleString("Foo"))
  23. println(doubleStringList(List("Foo", "Bar", "Baz")))
  24. }

As I mentioned previously, this example would be much improved by support for anonymous type functions in Scala. I am pretty sure Scala will eventually support them, as they would not require any deep changes in the implementation. They could be just implemented by desugaring to a higher-kinded type alias with a fresh name, but depending on when that desugaring is performed, it is possible that it would result in poor error messages. Supporting curried type functions is also quite desirable, but given my current knowledge of the internals, that seems like adding them will require some more elaborate changes.

I think Vincent Cremet was the first person to suggest this sort of encoding, and I vaguely recall reading about it on one of the Scala mailing lists, but I could not find the message after a little bit of time spent searching.

Comments (4)