uttumuttu

sunnuntai 13. syyskuuta 2009

C++ Kana #2 — Dependency Injection

This time we'll exercise templates and the typeid keyword to build a Guice-inspired dependency injection framework. Write class Injector that allows you to write


class IService {
public:
    virtual ~IService() {};

    virtual void serve() = 0;
};

class HelloService : public IService {
public:
    void serve() {
        std::cout << "Hello, world!" << std::endl;
    }
};

int main() {
    Injector injector;

    injector.bind<IService>().to<HelloService>();

    IService* service = injector.newInstance<IService>();
    service->serve();
    delete service;

    return 0;
}

C++ Kana #1 — Usable Logging

Let's exercise RAII and streaming. Write function line() that allows you to write

line() << "hello world " << 5 << "!";

transforming to

std::cout << "hello world 5!" << std::endl;

maanantai 20. huhtikuuta 2009

Arguing on Taste

A saying goes: "you can't argue with taste". This is neither true nor false: the saying is too ambiguous to warrant such classification.

This is a classical case of equivocation—the confusion of homonyms with synonyms. The problem is that the word "taste" has two distinct meanings:

1) The subjective response to a stimuli. These cannot be argued.

2) The ability to discern the objective merits (artistic, aesthetic, culinary, etc.) of a work. These can be argued.

Lest this sounds too cold-hearted and ridding of emotions, it should be stressed that "having good taste" (of the second kind) may also suggest a positive correlation between one's subjective response and objective merits.

maanantai 16. helmikuuta 2009

Probabilistic Modus Tollens

Here's the rule that justifies the classical modus tollens inference:

[A => B] <=> [~B => ~A].

An example: if all kings have crowns, then not having a crown means one is not a king.

However, in real world, things aren't black-and-white. Therefore, a probabilistic equivalent of modus tollens would be nice. Here is one:

[P(A | B) >= P(A)] <=> [P(~B | ~A) >= P(~B)].

An example: if kings have crowns more often than other people, then not having a crown decreases (or doesn't increase) the probability of one being a king.

Proof is left as an exercise. (Hint: apply Bayes's rule and the rule of complementary probability.)

torstai 18. joulukuuta 2008

Static Checking is Defensive Programming

(But not vice versa.)

void openFridge(Object object) {
____if(!(object instanceof Fridge)) {
________throw new IllegalArgumentException("!(object instanceof Fridge)");
____} else {
________((Fridge) object).open();
____}
}


void openFridge(Fridge fridge) {
____if(fridge == null) {
________throw new IllegalArgumentException("fridge == null");
____} else {
________fridge.open();
____}
}

perjantai 13. kesäkuuta 2008

Reuse as a Refactoring Strategy

There has been a strange pattern reoccuring in a lot of my development efforts. Often, when I write a component X acting as a backend ("server"), and a component Y acting as a frontend ("client") to X, a very good way to refactor the code is to write another component Z also acting as a frontend to X. You start to notice redundancies between the frontends Y and Z, so you move the redundancy towards the backend X: the dividing line between the responsibilities of the frontend and the backend becomes clearer.

In retrospect, this seems obvious: the best way to write reusable code is to reuse it. However, more paradoxically, I believe such deliberate reuse can be used as a refactoring strategy even if you never plan to use Z.

I wonder if this idea already has a name.

tiistai 27. toukokuuta 2008

Inferential Duck Typing, or: Type Hierarchies Considered Harmful

Let's begin with some Java code:

interface File {
    void open();
    void close();
    void delete();
}

interface Fridge {
    void open();
    void close();
    void addSticker(Sticker s);
}

class Util {
    // Too bad this version only works for Files, not Fridges.
      
    void with(File file, Runnable r) {
        try {
            file.open();
            r.run();
        } finally {
            file.close();
        }
    }
}


How can we make Util.with work with both Files and Fridges? Obviously, by crafting an interface IOpenClose and making both File and Fridge extend it:

interface IOpenClose {
    void open();
    void close();
}

interface File extends IOpenClose {
    void delete();
}

interface Fridge extends IOpenClose {
    void addSticker(Sticker s);
}

class Util {
    // This works for both Fridges and Files.
      
    void with(IOpenClose obj, Runnable r) {
        try {
            obj.open();
            r.run();
        } finally {
            obj.close();
        }
    }
}


Unfortunately, the above version requires us to go through all code and mark all open-closeable classes and interfaces with IOpenClose. This is simply superfluous: everyone with two eyes can see that both Fridges and Files provide open and close methods, so why should this be marked with an additional hierarchy parent (IOpenClose)? Not only humans can see this, but a compiler can automatize it.

Here's what I propose: the language should provide implicit downcasting between classes and interfaces with compatible methods. Perhaps this shouldn't be the default behavior, but at least it should be possible. Here's an example:

interface IOpenClose {
    void open();
    void close();
}

interface File {
    void open();
    void close();
    void delete();
}

interface Fridge {
    void open();
    void close();
    void addSticker(Sticker s);
}

class Util {  
    void with(IOpenClose obj, Runnable r) {
        try {
            obj.open();
            r.run();
        } finally {
            obj.close();
        }
    }

    static void main(String[] args) {
        with(new File("temp.txt"), new Runnable() {
            public void run() {
                System.out.println("Hello, world!");
            }
        }
    }
}


OK, so the change wasn't really that big. But suppose Java did support operator overloading. Then look at this example:

interface VecArithmetic<E> {
    E operator + (E rhs);
    E operator * (E rhs);
}

class Vec3<E extends VecArithmetic<E>> {
    public E x;
    public E y;
    public E z;

    public Vec3(E x_, E y_, E z_) {
        this.x = x_;
        this.y = y_;
        this.z = z_;
    }

    public Vec3<E> operator + (Vec3<E> rhs) {
        return new Vec3<E>(x + rhs.x, y + rhs.y, z + rhs.z);
    }

    public Vec3<E> operator * (E rhs) {
        return new Vec3<E>(x * rhs, y * rhs, Z * rhs);
    }
}


This version should work for integers, floats, doubles or complex numbers (class Complex), or any type that defines right-addition and right-multiplication.

So, why would such implicit downcasting be preferable?
  • First, it doesn't require changing existing classes.
  • Second, classes such as Vec3 above, can effectively define a set of methods that they require from parameterized types. For example, some classes may only require operator +, where others require both operator + and operator *. The same effect could be achieved by constructing an interface hierarchy with interfaces IAdd and IMultiply, but this is superfluous and redundant.
  • Third, implicit downcasting is very easy to check statically. In the above example, the compiler doesn't have to work through the whole class Vec3 to see that it requires operator + and operator * from E. The legitimacy of the type parameter is only checked upon downcasting. This is vastly easier implement than C++ way latent typing, at the cost of some boilerplate code (e.g., interface VecArithmetic).
  • Ultimately, this method reduces what could be termed hierarchy interdependency: the class Vec3 and its parameterized type E no longer depend on common type hierarchy. This often obviates the need for adapter classes.
But are there any downsides? Well...
  • Explicitly marking a class C to adhere to some interface B increases documentability. If you know that "C is a B" and "A uses objects of type B", then you know objects of type C can be used by A. In the implicit conversion model, we only know that "A uses objects of type B": on the other hand, the classes conforming to interface B can be deduced automatically.