#1280 Singleton pattern / Static slots

DJCordhose Sat 30 Oct 2010

Folks!

I wondered if there is anything like a direct language construct to express the singleton pattern. Like the "object" keyword in other languages. I could not find anything like that.

What ever the answer is, what is the philosophy behind it?

A more general question: What are the supposed use cases of static methods / fields except for the "singleton" and "factory" pattern.

Could it even be a good idea to get rid of all the static stuff? And replace them by higher level language features for each use case?

Thanks in advance

  • Oliver

DanielFath Sat 30 Oct 2010

Hi, Oliver. And no Fantom doesn't have an object keyword to make singletons. IIRC Scala is one of the rare few programming languages that does that.

You use static anytime you require some operations to be performed before/without a single class instance. For example take fwt::Monitor.primary. It makes sense to be able to get a primary monitor of a system even if you haven't created a single monitor object. Also static methods can't use this since there classes don't allocate instances in static context.

Getting rid of the static - this one is a bit of a puzzler. What static method truly are Functions attached to the Class namespace. You'd probably could use reflection to get these functions but in the end I'm not sure if the solution would be more elegant than what we have now.

Checkout wikipedia article on Singleton drawbacks.

DJCordhose Sat 30 Oct 2010

More generally: I know how static methods and fields work, but what would you use static fields and methods for other than implementing singletons and factories? Are there other use cases? If not than you could just drop static fields/methods and instead have something for those use cases, right? If there are other use cases couldn't we just have them explicitly in the language?

The aim is to discuss if static stuff is just too level and error prone and could possibly be replaced by something more high level.

brian Sat 30 Oct 2010

From a philosophy point of view, I don't necessarily think static const fields or methods are bad. Sometimes singletons and and statics get abused in Java, but so does every feature ;-)

A good example of the singleton pattern in Fantom is sys::Env.cur.

Remember that singletons have to be const if they are static:

const class Foo
{
  static const Foo cur := make

  private new make() { ... }
}

DJCordhose Sat 30 Oct 2010

Totally agree. Obviously I am not good at expressing my point.

Let me try again:

Would it be an option and worth discussion to express this

const class Foo
{
  static const Foo cur := make

  private new make() { ... }
}

which does not describe the WHAT, but rather the HOW and is thus low level into something expression more high level just WHAT you want, namely a singleton? Like maybe

const singleton Foo
{
  private new make() { ... }
}

(and under the hood translate it to static stuff as before).

Then do this for all use cases of static slots / methods and finally get rid of static.

Why would anyone do that?

Replacing low level constructs with high level constructs. Making patterns explicit, just like you have done with the ? modifier to make clear a value can be null. There are many more places in the Fantom language where you made a pattern explicit instead of letting the programmer repeat himself over and over, possibly making mistakes. Which is a very good idea IMHO.

Does this make more sense now?

Oliver

DJCordhose Sat 30 Oct 2010

Oh, and to make my intentions clear:

I have a Java background and am looking for a simple language (that's why I do not choose Scala without saying it is bad) that makes it easy for my target audience to write code without having to look up stuff in a patterns book or study the lambda calculus or anything theoretical at all.

Fantom has a lot of patterns included as first class citizens (there even is the once keyword), that's why I wondered: Why not for singletons? And then: Why not for factories?

Oliver

go4 Sat 30 Oct 2010

I know the static reduce the chance of reuse. Singleton should be promoted.

yachris Sat 30 Oct 2010

There's quite a history behind the "static" keyword; unfortunately, I'm tempted to say that it reflects some lazy thinking on the part of the language designers.

In "C", the word "static" had two (slightly) related meanings. Applied to a function, it meant that the visibility of that function would be limited to the compilation unit that that function was defined in. Applied to a variable, it meant that the variable's extent would last beyond the function it was defined in (essentially, it made it a global which was only visible in the function).

C++ kept the "C" semantics, and added the functionality that when "static" was applied to a field or function in a class, then that meant that that field or function was a "class" field or function. This meant (for a field) that it would have to have storage defined outside the class and that the field would not be bound to one specific instance, but available to all. For a function in a class, that function would similarly not get a "this" pointer. Similar to "C", the field would in effect become a global, but you got to define the visibility of that global.

Java picked up the "make a method/field not have to take an instance" semantics, and dropped the "C" semantics.

Fantom seems to be following the Java semantics.

So, we have history on our side :-) and there are good use cases that have come out of this.

Specifically, the static field is definitely useful for defining constants in a single place, e.g. "static const Int FileReadSize := 4096", so that you only have to change one thing if you want to adjust your read size for different situations.

For methods, singleton and factory have already been mentioned, and it's also worth mentioning the "Utility Function" pattern, although Fantom needs these a whole lot less since Float, Int, etc. are actual objects, so you can have "Int#max", etc. defined on the object itself, where they should be defined.

It's probably worth mentioning that the C++ standards committee decreed that anyone wanting to add a new keyword to the language would have to give up a major organ from their body to do so. Evidently they've been fairly lax on enforcing this rule :-)

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

SO WHAT...

Well, I think that perhaps the "static" in

static const Str LoggingFileName := "log.txt"

is redundant... saying const for a field could imply static. It'll never change, why would you want it taking up space in instances?

DanielFath Sat 30 Oct 2010

That's a nice summary yachris. BTW what do you mean imply static? Require static (as in throws an error if it's not static) or just act as static? If it's the latter than that seems somewhat counter-intuitive as const can be used in context of field and class and carries similar meaning.

@DJCordhouse: Well, yachris already explained it better than I could.

As for concertizing design patterns. I don't think its necessary or even productive to make all(or most of them) of them into language features. I think it's in design pattern nature to emerge constantly from practice.

yachris Sat 30 Oct 2010

BTW what do you mean imply static? Require static (as in throws an error if it's not static) or just act as static? If it's the latter than that seems somewhat counter-intuitive as const can be used in context of field and class and carries similar meaning.

I meant the "acts as static" case, but only for fields. Given nearly five seconds of thought :-) I can't come up with a case where a field declaration of const static Int foo := 11 would be semantically different from const Int foo := 11 (and, in fact, it would save memory in each instantiated instance), so I think the static keyword is redundant here.

As for concertizing design patterns. I don't think its necessary or even productive to make all(or most of them) of them into language features.

I agree that we don't want to somehow instantiate all possible design patterns in the language.

I think it's in design pattern nature to emerge constantly from practice.

Absolutely spot on. Of course, once something (like Singleton, as an example) is enough of a pattern, it might make sense to allow it to somehow enter the language, so that you don't have to say, "Oh Singleton, sure, you can do that, just make the constructor private and declare a static method that returns the one true instance!", you can just say, "Oh Singleton, sure, you just declare class singleton Foo {...}".

jodastephen Sat 30 Oct 2010

This came up before in `http://fantom.org/sidewalk/topic/404`.

Basically, Fantom definitely needs to have a singleton/object concept. Design patterns sometimes (but not always) capture failures in language design. The "javabean" design pattern is a failure to provide properties. Singleton is definitely one that should be encoded.

However, as I pointed out before, the singleton needs to be able to implement mixins. This completes the circle in an OO language. Without it, Fantom is not an "everything is an object" language.

class Person {
  singleton {
    // static stuff
  }
  // instance stuff
}
OR
singleton {
  // static stuff
}

ie.'singleton' can be embedded in a class or at the top level, if there is no instance part.

class Person : ClassSuper {
  singleton : SingletonSuper {
    // static stuff
  }
  // instance stuff
}
Person p := Person()
Obj o := Person
SingletonSuper s := Person

Here, both the class and singleton implement mixins. The singleton can be assigned to Obj or SingletonSuper.

The main issue is compilation to bytecode and what the type object looks like. My view is that one type object represents both the instance and singleton parts of the combined "class". My preference is for the compilation to produce a genuine bytecode class if the singleton implements a mixin, otherwise to use bytecode statics. If bytecode statics have been used and the code then assigns it to an Obj, then a class should be generated dynamically at runtime representing the static method.

When referred to, I want Person to be treatable as the name of the singleton object. Achieving this isn't easy (I prefer insisting on all classes starting with an upper case letter, and all variables with a lower case letter).

rosarinjroy Sat 30 Oct 2010

is redundant... saying const for a field could imply static. It'll never change, why would you want it taking up space in instances?

I think implying that "every const is static" is not correct. There are instance specific const fields as well. For e.g. a person object's first name and last name may not be modified once an instance is created. One can come up with a handful of examples.

I agree with Brian on singletons being immutable objects. I cannot also think of a straight forward way to create a singleton that is mutable (i.e. it is not a const field) and thread safe. As at the language level you don't have primitives for mutual exclusion, you have to rely on a single Actor instance to achieve the mutual exclusion. It gets a little bit quirky to do support a mutable singleton.

For those who may be interested, here is one possible way of implementing a mutable singleton:

using concurrent

// This is the class for the singleton object we want.
class TargetMutableSingleton {
   Int numInvocations 
   public Int invoke() {
     numInvocations++
   }
   public new make() { numInvocations := 0 }
}

// This is the container for the singleton instance. This container
// holds a reference to the Actor, which in turn holds the onlyInstance of the
// singleton in its locals. This is done this way for achieving mutual exclusion. 
const class MutableSingleton {
   public static const MutableSingleton mutableSingleton := MutableSingleton()

   private static const Actor singletonHoldingActor := Actor(ActorPool()) |Obj? -> Int| {
     TargetMutableSingleton onlyInstance := Actor.locals.getOrAdd("onlyInstance", 
     		|Str key -> TargetMutableSingleton| { return TargetMutableSingleton() })
     return onlyInstance.invoke
   }

   public Int invoke() {
     singletonHoldingActor.send(null).get
   }
}

// Test class.
class Main {
   Void main() {
      echo(MutableSingleton.mutableSingleton.invoke)
      echo(MutableSingleton.mutableSingleton.invoke)
      echo(MutableSingleton.mutableSingleton.invoke)
      echo(MutableSingleton.mutableSingleton.invoke)
   }
}

brian Sun 31 Oct 2010

Given all the various things programmers do that require boiler plate, I am not sure singletons is the most productive feature to spent our complexity tax on.

However the topic Stephen referenced was a very great discussion, and I do believe there is value in exploring this issue further.

Consider the Env class which you old timers remember used to be Sys with static methods. I changed Sys into Env to make all the static methods virtual (stdout, props, etc) so that the community could create alternate, more sophisticated environments such as OSGi based.

On the implementation side, Env is just a normal OO class with virtual methods - I don't necessarily think there is anything complicated about that which requires fixing.

In my mind the part that sucks is that now instead of using Sys.out, I have to use Env.cur.out. So to me the real problem with singletons is more on the client side - how do I access a singleton more concisely?

If we had a conceptually lightweight way to do that which was backward compatible (or at least not super disruptive), I would be in favor of that.

One of the simple ideas I had was just create a Singleton facet which could be used to mark an accessor method so that the virtual methods on the instance could be used statically.

go4 Mon 1 Nov 2010

I think that I have a good idea.

a class like this:

const class Person{
  const name
  static Void foo(){

  }
}

be compile to:

const class Person{
  const name
}
const class Person$:Type{
  static const Person$ _instance:=Person$()
  private new make(){}

  Void foo(){
  }
}

It's only a compiler magic.We can use singleton implicitly:

Person.foo()
Type t:=Person

No increase the complexity,and we get a powerful feature to override static method. In this way,we not need the type literal at all:

Person.isMixin

Login or Signup to reply.