Daniel Spiewak apparently just presented at Oscon records-of-lambdas as a place where statically typed OO/FP could learn to love each other. PDF: http://bit.ly/pYFTbG Keynote: http://bit.ly/qIPBos
Charlie Nutter said he could see himself using it, which made me all kinds of happy, because that's precisely the space I'm digging at with my language, Ashlar. Charlie asked via Twitter that I kick off a conversation on this stuff here on the JVM Languages group, so that's what I'm doing. The gist in Ashlar is that every name refers to a function. Some functions return records of named lambdas, which provides OO style functionality. This is statically typed, however, in a structural way (akin to OCaml's class system). This is precisely what Daniel was talking about. I'm also doing a few other things with Ashlar, but they're icing on the cake on top of this core approach. Although I could in theory try to generate all the possible mix-matches of types (by basically implementing a system like Scala Traits, but apply-able at *any time*), in practice this is very hard, and generated an annoying amount of code. My earlier take on this point, the language Cornerstone, was chasing this angle. With Java 7, though, I shifted to a MethodHandle approach. Although this is very statically typed in the user space, the resulting code is So, take this Ashlar code: let foo = { x, y -> x.plus(y) } This is compiled into a class with a "foo(Object x, Object y)" method, and a "MethodHandle foo" static final field (as well as, for the moment, a redundant "MethodHandle foo" instance final field), whose value is a method handle pointing to the foo method. The implementation is to do an indy to retrieve the "plus" field off of "x" (we'll return to this later), and then invoke that using x and y. The type checking to ensure that x has a member "plus" holding a lambda which can accept a y is done at compile time, and bound at runtime. So, in this sense, Ashlar is a very statically typed language to the user with a very dynamic feeling implementation. Now, since I want to cling to decidable completeness, I'm sticking to a variant type like approach for the base classes. So you can declare something like this: type Foo = Bar(a:1) | Baz(a:2) with { let plus = { x -> a.plus(x) } } This compiles into three classes: *) an abstract base class "Foo" with an abstract method "a" requiring an integer to be returned, an instance field "a" that points to a method handle returning an integer, as well as the "plus" method and field, and *) the two implementing classes "Foo$Bar" and "Foo$Baz" that implement the "a" abstract method. If you want to use an extended version of Foo, you can use the clone version: type BigFoo(Foo) |= Bash(a:-1) This generates a new abstract class extending Foo (Foo$BigFoo), as well as a new concrete class (Foo$BigFoo$Bash). Note that Bash cannot be passed in for a Foo, although any Foo can be passed in for a BigFoo. Types are also assignable: type Fab = Foo For the moment, this is just handled at the compiler level, and doesn't change code generation. Types are also extensible: type Fab = Foo with { let minus = { x -> a.minus(x) } } This generates a new abstract class with the new member. And types are re-nameable: type Foo = Foo with { let times = { x -> a.times(x) } } How this works by generate code to hack all instances of Foo within scope (see hacking instances below). There's syntactic sugar which allows you to leave out the "Foo =" bit right after "type", but it's the same thing. You can't extend Foo globally, but that's mostly my conservatism coming through. It feels like it's stepping into a hornets' nest, though. You can also describe a "virtual type", which is akin to an interface, except that anything matching its structure counts: type Virt = with { let times:Integer->Integer } Once you have an instance of Foo, you can mangle it to your heart's content. let hack_foo = { b:Foo -> b with { let times = { x -> a.times(x) } } } (You could leave off the explicit typing of "b", but then hack_foo would be badly named. And I have to admit, I like Daniel's syntax better at this point, and may well steal it, but there's an issue of consistency I'd need to take into account.) When you hack a type, the MethodHandle (with the instance curried in) is added to the "members" construct, which is queried by the indy to fetch methods. Currently, that's a hash table look-up followed by checking all the hard-coded names of fields. I'm considering an implementation which would take the name of the method as a string and compare it to a known string, and then return the MethodHandle if they match, null otherwise. When hacked a second time, this would then be wrapped by another MethodHandle to do another comparison. This would be done for some small "n" (3?), wherein we're still being nice to the JVM. But that's all optimization talk. Type inference on all of this is pretty straightforward down the H-M path, except that instead of failing to resolve types in ambiguous circumstances, we generate "synthetic virtual types" (i.e. structural descriptions of usage) and try to map onto them best we can. There are two main difficulties I have encountered at this point: pattern matching and error messaging. I had an issue with pattern matching implementation, but that was solved last week (at least in theory). The remaining issues both of them stem from the same underlying open question: how do I actually *talk* about this stuff to a human being? Say we have this code: let x = { a -> a.fetchSomething().doSomething(2) } The type of x is: 'a -> 'b (borrowing the OCaml convention of 'a, 'b, ..., 'z being "scratch"/unbound types) The type of 'a is: { fetchSomething:Unit -> 'c } The type of 'c is { :doSomething:Integer -> 'b } So, if we have this code, how should the error message read? type Boring = DoNothing let x = { a -> a.fetchSomething().doSomething(2) } x(DoNothing()) Similarly, how do the patterns in the matches look to describe structures? I haven't been working on Ashlar much for the last few weeks (studying for my GREs and doing my masters program's field education placement), but I've got an ASM 4.0 implementation that is about ready to be pushed to GitHub. I'm also gutting a bunch of the infrastructure integration (OSGi, Ivy), which works, but really slows things down, and it's confronting me with a few problems that probably shouldn't be solved until I get to the point where there's actually a language to speak of. ~~ Robert. Love Your Enemy: A Campaign to Regain Human Dignity Through Nonviolence http://www.mettacenter.org/mc/projects/love-your-enemy -- You received this message because you are subscribed to the Google Groups "JVM Languages" group. To post to this group, send email to jvm-languages@googlegroups.com. To unsubscribe from this group, send email to jvm-languages+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/jvm-languages?hl=en.