#404 Statically implementing mixins

jodastephen Sat 22 Nov 2008

This is an idea I've had kicking around for a long time, based on static implements implemented in Kijaro.

The basic concept is to allow a class to implement an interface statically. This sounds a little odd, but perhaps an example will help (Java style). Here is a normal factory interface, class and the factory implementation:

public interface Factory<T> {
  public T create(String str);
}

public class MyClass {
  ...
}

public class MyClassFactory implements Factory<MyClass> {
  public MyClass create(String str) {
    return new MyClass(str);
  }
}

Now, lets use static interface implementation instead:

public class MyClass implements static Factory<MyClass> {
  public static MyClass create(String str) {
    return new MyClass(str);
  }
  ...
}

As the code sample suggests, the concept is to allow a class to implement an interface in a static manner. In other words, instead of the interface being implemented using instance methods as per normal, it is implemented using static methods.

A key point is that there is nothing special about the interface. It is a normal interface that might be implemented in an instance way (as at present) or in a static way.

Of course, implementing the interface is only half the battle. The next part is referring to the static part of the class like an object:

Factory<MyClass> factory = MyClass.static;

So..., how does this translate to Fan?

Well, I'm raising this as I believe it is a key step forward in how we think about objects, effectively eliminating the difficulties faced when using static methods. Basically, it allows static methods to be used like instance ones - you can pass around an instance of the static part of any class.

Here are a couple of possible Fan syntaxes:

// option A
class MyClass : MixinImplementedByInstance : static MixinImplementedStatically {
}

// option B
class MyClass : MixinImplementedByInstance {
  static : MixinImplementedStatically {
    // all static code goes here
  }
}

// used by
MyClass.static
// or maybe just
MyClass

Note that none of this changes any existing Fan code. If you want to call a static method directly you still can:

MyClass.foo()

The change is that you can pass an instance of the static part of a class around. You can treat it as an object:

foo(MyClass.static)

(I'd like to drop the .static part, but that would require a single namespace for classnames and variable names, or forcing all classes to start with upper case, and all variables to start with lower case)

On implementation, there would need to be a hidden bytecode class that actually represents the static parts as a normal object:

class MyClass$$GeneratedStatic : MixinImplementedStatically {
  Void methodFromMixin() {
    MyClass.methodFromMixin()
  }
}

I suspect that the implementation could be generated on demand by the classloader, rather than pre-compiled (thus saving space in the pod/jar).

Finally, why would we want this?

Well, Java and other OO languages are plagued by the creation of addition classes such as factories which provide an OO object hook into the main OO instance. While closures eliminate some of this pain, there will always be cases where the pain remains. Allowing direct access to the static part of a class as though it is an object is thus a major gain in removing unecessary boilerplate.

I should also note Gilad Bracha as an inspiration, although I'm not suggesting going that far.

katox Sat 22 Nov 2008

Interesting thoughts, Stephen. I think all of this leads to a decision on constructor/with-block/threadLocals debate. But that's a very hard nut to break.

I made a very similar observation as Gilad in his posting but I don't understand what solution he offers.

I think that an ideal solution for Fan would transform factories, constructors and with blocks to a single logical construct (on language level, not neccessarily on JVM level).

The problem is that if you want to create an object you mostly need to give it context information from outside. You can do that by using multiple constructors (inflexible), with-blocks (not easily reusable) or factory methods (using statics). Once you decide that you want to change the object construction pattern you have another problem.

If you throw in statics, singletons, threadLocals and global (system) state the problem gets complicated even more ... or maybe not, it is just the flipped side of the same coin.

brian Sat 22 Nov 2008

Interesting post Stephen. Let me share my thoughts. First I make a distinction between programming "in-the-small" versus "in-the-large". Programming in the small is doing the mundane things like parsing input - these are tasks ideally suited for static, strongly typed OO. However programming in large is where we are concerned with system architectures and how software components are glued together - this tends be where factories come into play. My personal opinion is that a Java-like-OO type system tends to impose an extremely high complexity tax. So this is a case where functional and dynamic programming can greatly simplify the solution. In fact what you propose is just about already doable in Fan using dynamic programming.

Let's look at what is available today and the techniques I would use:

Functions

Functions seem a much more elegant way to handle factory like interfaces - especially when there is only one method (which factories tend to have). This allows us to create factory functions from instance methods, static methods, or closures easily:

f := &factoryInstance.create
f := &StaticFactory.create
f := |Str->MyClass| { ... }

Namespaces

The intent of Fan is to use Uris and namespaces to glue system components together. Often we use services for this job:

SqlService s := `fan://sys/service/sql::SqlService`.get
s.sql("select * from Foo").query.each |Row row| { echo(row) }

This is what I mean by REST namespaces - using Uris to glue system components together at runtime.

Type Database

The type database is a very powerful technique for looking up a type based on a query:

parser := Type.findByFacet("parserFor", "text/html")

The cool thing about the type database is that it is inherently "pluggable" - to create new mappings between facets and types you just drop in a pod and the type database automatically re-indexes itself. This is the basis for how webapp and flux work.

Dynamic

But to get back to your original code, let's look at what you can do today. For a while I toyed with the idea of "virtual static methods", which are similar to what you proposed. But I found that with dynamic programming or reflection it didn't matter much. One of the cool things about named constructors is that they are interchangeable for factory methods during reflection:

class Foo { new make(Str) { ... } }
class Bar { static Bar make(Str) { ... } }

Foo#.make(["foo"])
Bar#.make(["bar"])

But perhaps one of the really cool things is that you can call constructors, instance methods, and static methods dynamically:

new ctor(Str s) { ... }
static Foo stat(Str s) { ... }
Foo inst(Str s) { ... }

x->ctor("id")
x->stat("id")
x->inst("id")

What happens is the "->" calls trap, which in turn uses reflection. Because reflective calls are flattened - we can treat instance and static methods uniformly. This sort of dynamic invoke turns out be very useful. One of the examples you linked to was with enums. We can do this today in Fan, given an enum instance or an enum type we can get the range:

Month.feb->values

The above technique almost solves your exact case (dynamically instead of statically), except we are missing one piece...

One of things Fan needs is the ability to get a "handle" to dynamically invoke static methods. If we have an instance we can use that like we did above. But it would be nice to get this "handle" straight from a type without necessarily allocating an instance - this is effectively what you are calling the "static part of your class". Originally I was going to implement Type such that it would route trap to the declared methods (rather than the methods on Type itself). But that doesn't work because because then you can't call any of Type's method dynamically (or at the least there might be conflicts). So let's say we create a new object under the covers which handles trap by routing to the declared methods:

class FactoryA
{
   MyClass create(Str id)  { .. }
}

class FactoryB
{
   static MyClass create(Str id) { ... }
}

This is our case where we really want a uniform interface to just call "create". Assuming we have our new staticPart method on type, we can do this for static and instance methods:

a := FactoryA#.staticPart
b := FactoryB()

a->create("id")
b->create("id")

So I do think we want something like "staticPart" - although that certainly isn't a very good name. By I guess my key point, is that I think this is the sort of stuff where we focus on enhancing Fan's dynamic features, not necessarily creating new complexity in the static type system.

jodastephen Sun 23 Nov 2008

I understand all the mechanisms available, but I disagree with the conclusion you came to.

It seems to me that we agree on the need for a staticPart concept. This provides access to a generated class as follows:

class Job {
  static Job create(Str id) { ... }
  static Job create(Str id, Int priority) { ... }
}

class Job$Static {
  Job create(Str id) {
    Job.create(id)
  }
  Job create(Str id, Int priority) {
    Job.create(id, priority)
  }
}

(I'd suggest that Job$Static can be generated on demand and is a singleton).

We've suggested two different syntaxes to get hold of the staticPart:

Job.static
Job#.staticPart

I don't think this especially matters.

The only difference we have is whether the object that is returned is allowed to implement a mixin contract or not.

It seems to me that not doing so is an anomaly in the language, pure and simple. There is a contract - the static part of any class does have a set of methods. So, why prevent the user from declaring that formally? Consistency in language design is a Good Thing.

brian Sun 23 Nov 2008

It seems to me that not doing so is an anomaly in the language, pure and simple.

I think this is really just a preference b/w static and dynamic typing - which often is just a matter of personal taste. The core issue here is creating uniformity between instance and static methods - trying to do that with the existing Java like OO type system doesn't seem right. But we already have it for reflection and dynamic calls.

So I'd say at this point I don't think it makes sense to complicate the language and static type system for this feature - it is certainly never a problem I've ever found myself wanting for. Can we come up with more compelling use cases?

Plus as an overall philosophy I think we need time to figure out how to use the features we have today before piling on more features.

However, this discussion has got me thinking about the need for alternate mechanisms for creating implementation of mixins (or classes). If you haven't already take a look at dynamic types. Another cool thing you could today is compile a class on the fly which implemented mixins. But I think there is some opportunity to do more - especially gluing mixin calls to functions.

Unrelated note: I just had Firefox auto upgrade itself, and all of I sudden my locator textfield doesn't show me my current URL - that is messed up

jodastephen Mon 24 Nov 2008

"Object Oriented Everything subclasses from Obj"

Fan home page. But static code is not accessible as an object. Should we change the home page?

I think this is really just a preference b/w static and dynamic typing - which often is just a matter of personal taste.

Not really. I'm comfortable with the dynamic aspect. I think this discussion is orthogonal to that. (I believe language features should be complete and consistent wherever possible)

The core issue here is creating uniformity between instance and static methods - trying to do that with the existing Java like OO type system doesn't seem right. But we already have it for reflection and dynamic calls.

You seem to be arguing against yourself. Reflection, functions, currying and dynamic calls handle instance/static in a unified way. Why not the basic type system itself?

I also don't believe this is a new feature, but a completion of an existing feature. It only feels radical because Java doesn't do it (and its even more needed there).

The main use case is likely to be one only experienced over time. Somebody writes one part of a system using instance methods. Somebody else, at another point in time, writes part of the system using static methods. Later, a third person wants to unify the code.

Its certainly not going to find much of a use case in well-designed core libraries written by a small closely knit team (as most Fan code currently is). This is a feature for consistency and language longevity.

helium Mon 24 Nov 2008

Later, a third person wants to unify the code.

Rant: Aren't you allways pretty much fucked in a nominally typed object oriented system if you want to do that?


What you suggest is pretty much what Scala has. Scala doesn't have static "methods" but a companion object, which is a singleton object which can have the same name as a class. In Sala it's more important though as Scala unifies it's object system with a ML like modul system. So an object corresponds to a module.

brian Mon 24 Nov 2008

Giving it some more thought kind, what we are talking about is exactly what Helium pointed out what Scala has with object - making a class a singleton. I kind of have mixed feelings about singletons, but since Fan doesn't really let you have mutable static state it is sort of a moot issue. I'm not sure I see the path for the original proposal where static methods implement an instance based mixin - I just can't fathom that direct use case. Although it does touch on the subject I mentioned earlier where you want to declare "virtual static methods" - this happens all the time any simple must declare a static method (or ctor) called fromStr - we just do that dynamically today. And there is definitely something there in the back of my mind about implementing class/mixin type contracts easier and gluing them it to existing code (which might be framed as the more general purpose problem statement).

jodastephen Mon 24 Nov 2008

The fromStr case was the one I'd forgotten about.

I think of it more in terms of contracts. The rule in Fan is that a simple type must define a static method named fromStr. What I want is to be able to express that contract in code rather than documentation.

mixin SimpleFromStr {
  This fromStr(Str str)
}

class Int : static SimpleFromStr {
  static Int fromStr(Str str) { ... }
}

One alternative for this use case would be virtual static methods as you describe:

mixin SimpleFromStr {
  static abstract This fromStr(Str str)
}

class Int : static SimpleFromStr {
  static Int fromStr(Str str) { ... }
}

This latter is more powerful in one way - the mixin can define both static and non-static methods. But it is less powerful in other ways - you can't have an instance and static implementation of the same mixin.

And yes, its a neat way to have a singleton. But maybe Fan's lack of mutable shared state makes that aspect less significant.

brian Mon 24 Nov 2008

One alternative for this use case would be virtual static methods as you describe

That was the original way I was going to handle it, but its never felt quite right.

I think of it more in terms of contracts.

I am definitely thinking also in terms of contracts. That is why I'd really like to frame this discussion in terms of a more general purpose problem: more flexibility to implement mixin/class contracts than traditional sub-classing.

We already have a uniform interface for calling instance and static methods as functions. And we already have a good way to specify contracts as a set of slots via classes/mixins. I think what we are lacking is more flexibile ways to glue the two together both at compile time and at runtime.

katox Wed 26 Nov 2008

It seems to me that unification of constructors and factory methods, static and instance calls is doable with the singleton approach. Plus, I think Stephen is right with the fromStr example.

Login or Signup to reply.