Monday, January 31, 2011

Replies to comments

steck said...
Haskell type classes give a consistent interface to different kinds of numbers (among other things). Would that do here?

It would solve the problems with the built-in number hierarchy. Does Haskell allow you to add user defined subtypes to the built-in number types?

Jevon said...
Are you saying that we shouldn't develop using design patterns? No, there's nothing wrong with the pattern per se. I'm saying that if we have programming concept that is important enough and ubiquitous enough that we can identify it and name it (for example, “The Factory Pattern” or “Singletons”), then there should be a way of abstracting it so that the pattern is replaced by something much more lightweight, or, if possible, nothing at all.

You can't argue against Factory objects using Factorial as an example. That's like arguing against using Windows when you're using Notepad as an example.
Notepad might be an effective example for certain types of arguments. Factorial obviously shouldn't be encapsulated and turned into a first-class object, it's absurd. But the language does require that it be encapsulated in some kind of object, nonetheless.

Michael said...
Java is verbose, way too verbose in areas that should be concise.
True, but I'm not necessarily complaining about the verbosity. I'm complaining that there is no reasonable way to avoid, eliminate, or abbreviate the verbosity.

Hardware Critic said...
His point is that Java has created a culture where developers over-generalize (violating the en vogue You Ain't Gonna Need It principle) and create frameworks rather than customized solutions. You turned it on its side and asked "why should developers ever have to do this junk" and made it a criticism of the Java language.
Just so.

But in doing so, you take code decorated with "don't do this" and then point to it as an example of Java's failings.
You say "but what about the 1% of people who need all that extra nonsense?", but remember Mr. Woody's point is that developers should create bespoke solutions providing only the power necessary to solve the problem at hand rather than large general frameworks.


I don't think we are contradicting each other. Let's take the “Singleton” pattern as an example. Mr. Woody and I both agree that a Singleton would be an unnecessary complication to the factorial class, but we disagree on why. If you look at the Wikipedia article on singletons, you'll see that there are three examples of how to write singletons in Java:
// Examples taken from Wikipedia
public class Singleton {
 
    private static final Singleton INSTANCE = new Singleton();
 
    // Private constructor prevents instantiation from other classes
    private Singleton() {
    }
 
    public static Singleton getInstance() {
        return INSTANCE;
    }
 
}

// Solution suggested by Bill Pugh
public class Singleton {
 
   // Private constructor prevents instantiation from other classes
   private Singleton() {
   }
 
   /**
    * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
    * or the first access to SingletonHolder.INSTANCE, not before.
    */
   private static class SingletonHolder { 
     public static final Singleton INSTANCE = new Singleton();
   }
 
   public static Singleton getInstance() {
     return SingletonHolder.INSTANCE;
   }
 
 }

// Modern way suggested by Joshua Bloch
 public enum Singleton {
   INSTANCE;
 }
All three are missing my point. They say, “The correct way to use singletons is to write your code like this.” What they ought to say is “The correct way for an implementation of singletons to behave is as if your code were written like this.”

A programmer who decides to use singletons (correctly or incorrectly) ought to be able to write something like:
public singleton FactorialUtil
{
    public static int factorial(int n)
    {
        if (n == 0) return 1;
        return n * factorial(n-1);
    }
}
Yes, it is stupid to do this for something like the factorial program, but it clearly states my (poorly considered) intention, was trivial to do, trivial to undo, and best of all, theoretically allows me to change the implementation of singletons from “traditional” to “Pugh style” without having to look at all the places singletons are used.

I'd have to post a much larger program and then spend time arguing the merits of singletons if I had to give a “real world” example.

Your point that Java requires significant boilerplate in order to say what needs to be said is valid, if not particularly novel. Even so, using code which was written in an intentionally convoluted way to criticize Java's convolution is like using the winner of the Obfuscated C Code contest to demonstrate that C is difficult to read.
It is hard to make concise arguments against convolution without needlessly convoluting a concise piece of code. A situation like this in real life usually involves a truly huge pile of awful code which makes the blog post far too large and contains many additional problems that hide the point I'm trying to make.

Jason Wilson said... The Development Chaos Theory article is largely concerned with factories. Factories are used all the time in Scheme and nobody ever complains because both the use and definitions are easy and natural:

USE:
((*factorial-function-factory*) 5)

POSSIBLE DEF:
(fluid-let (*factorial-function-factory*
(lambda (n) ....))

How *factorial-function-factory* got defined should not matter to the code depending on it. It's there. I promise. (Guice gives absolutely no more guarantees that it's defined and it is the best in class dynamic variable err, dependency injection framework for Java).

The Development Chaos Theory article is of course ridiculous for treating fact as a function that needs to be so abstracted, but without dynamic variables, this really is a necessary pattern in Java in many real world cases.

Exactly. There's just one refinement I'd suggest. (let ((factorial (make-instance <factorial-function>))) (factorial 5)) Object frameworks like CLOS provide a ‘universal factory’ generic function. And I'd suggest this definition of <factorial-function>
(defclass <factorial-function> () (:metaclass singleton))
The definition of the singleton metaclass would contain code analagous to Bill Pugh's implementation.

But this doesn't answer Mr. Woody's objection that this is completely unnecessary. In that case, I suggest this alternative definition of <factorial-function>
;; Unnecessary
;; (defclass  <factorial-function> () (:metaclass singleton))

(defmethod make-instance ((class (eql (find-class '<factorial-function>))) &rest initargs)
    #'factorial)
I have overloaded make-instance in this one case to not even instantiate an object but to return the factorial procedure as a natural singleton.

2 comments:

gwern said...

> Does Haskell allow you to add user defined subtypes to the built-in number types?

Only knowing Haskell, I'm not sure what you mean in Haskell terms.

If I disliked the Num instance for Integer, say, I could define a newtype for Integer ('newtype MyInteger = MyInteger Integer' and then define a new instance of Num ('instance MyInteger Num where (+) = ...').

Paul Steckler said...

Here's the Haskell type for +:

(+) :: (Num a) => a -> a -> a

You can make new instances of the Num type class (just like you can write your own instances of the Monad class). A silly example:

data Foo = Foo
deriving (Show,Eq)

instance Num Foo where
(+) Foo Foo = Foo

With these definitions, the following is a valid expression:

Foo + Foo