3

In Object Oriented Programming, we're taught to think in terms of Polymorphism (the idea that the implementation is decoupled from the interface - and that it makes sense to think of the interface first).

This is expressed by Eric Gamma (author of Gang of Four Design Patterns Book)

Program to an interface, not an implementation

Later we learned that this is a way of solving The Expression Problem.

Other languages such as Clojure address 'The Expression Problem' using protocols.

Now coming out of Scala (and perhaps influenced by Haskell) we're seeing a design movement that says "Design your Types First". When I heard that - I thought I'd heard it before.

My question is: Is 'design with types first' ultimately the same as 'design with interfaces first'?

3 Answers 3

3

In Java-like languages, interfaces describe object types, classes describe abstract data types. "Design with interfaces first" is not the same as "design with types first", simply because in a Java-like language classes (and primitives) are also types (but not object types). So, "design with interfaces first" really just means "do OO".

However, "design with types first" doesn't exactly mean what you think it means. The idea is that you use types to model your problem domain and let the types drive the design and development. It's more analogous to TDD in that sense: in TDD, the tests tell you what to do next, they tell you what to do, they tell you how to do it, they tell you what your design is, they tell you how to structure your code, and they tell you when you are done.

I would call it maybe Type-Driven Development (which conveniently and confusingly can also be abbreviated to TDD), because you use types in the same way that you use tests in TDD. It's not about designing with types first, it's about the types driving everything – designing them first is merely a prerequisite for that.

2
  • This is brilliant. I hear lots of Haskell guys say they don't use TDD for this reason.
    – hawkeye
    Commented Sep 5, 2014 at 0:35
  • @hawkeye I don't understand. This reply talks about the benefits of TDD. So what is the reason your thinking of which convinces some Haskell devs not to use TDD?
    – Andrusch
    Commented Jan 30, 2021 at 9:20
3

A small case study: a pattern matcher in two pseudo-languages, first OO and then functional

In OOP, designing interface-first

interface IPatternMatcher {
    bool Match(string toMatch)
}

The above is the only interface we need, but it doesn't really give us any clues about how to design our implementing types. You will probably want some composites, so you implement an OrPatternMatcher

class OrPatternMatcher : IPatternMatcher {
    OrPatternMatcher(list<IPatternMatcher> submatchers);

    bool Match(string toMatch) {
        return _submatchers.Any(s => s.Match(toMatch));
    }
}

But now you have started implementing functionality, without having finalised your hierarchy. It is kind of impossible to separate these two stages in OOP because your functions and types are so intertwined


In FP, designing type-first

type PatternMatcher =
| OrPatternMatcher        : list<PatternMatcher>
| AndPatternMatcher       : list<PatternMatcher>
| SubstringPatternMatcher : string
| RegexPatternMatcher     : regex

Here, we have all the necessary types designed up-front, covering all the cases you can think of. The PatternMatcher union type in FP is related to the IPatternMatcher interface OOP, with the crucial difference that the union declares its type structure with no functions, whereas the interface declares its functions with no type structure.

It is now trivial to implement a match function just by looking at the shape of the types you have laid out. If you have designed the types sensibly for your domain, you are already most of the way to having a working component. This is what it means to design your types first - to accurately model your domain without worrying about behaviour

In my opinion OOP interfaces and FP types are almost the opposite of each other, and the benefits gained from designing your FP types up-front are very different to those gained by programming to an interface.


As an additional afterthought, the phrase "design your interfaces first" is not the same as "program to an interface" and so the latter cannot be truly compared to "design your types first".

  • "program to an interface" advocates the decoupling of components by not assuming implementation details
  • "design your types first" is advice on modelling your domain sensibly before implementing behaviour

The two really aren't that comparable, as they relate to different situations

8
  • 1
    @RibaldEddie What separate interfaces?
    – Alex
    Commented Sep 4, 2014 at 15:29
  • 1
    I agree with the "afterthought". But everything before that just seems like a rant about the open world assumption made by interfaces (and type classes too, mind you!) and how it's worse than a closed world assumption (conveniently ignoring the other side of the expression problem coin).
    – user7043
    Commented Sep 4, 2014 at 16:13
  • 1
    Really appreciate the time you put into this comment. I can't help but feel that your afterthought undermines your position. For me - I could swap around your explanation of your two points and it would have the same meaning. Which is the point of the original question.
    – hawkeye
    Commented Sep 5, 2014 at 0:34
  • 1
    It seems to me that you are conflating the general concept of an interface and the particular Java (or similiar language) construct of an interface. The interface is everything about how client code would use an object, not simply those parts which would actually be in an interface. When advocating programming to an interface, the point is not that we should always be working with interfaces. Rather, the point is that we should focus on how the object is intended to be used, rather than how it produces that effect. Commented Sep 5, 2014 at 1:07
  • 2
    In this particular case, you should start by thinking about how the end user will use the class. The end user's isn't going to use this class by implementing any interfaces, so starting there is wrong. Instead, we should start by considering how the user will construct his pattern matching objects. Perhaps he'll use Pattern.substring("foobar") or Pattern.or(Pattern.substring("foo"), Pattern.substring("bar")) Once you've decided that you can worry about how to implement. Whether you use a pattern with various implementations, convert everything to regexes, or any other implementation. Commented Sep 5, 2014 at 1:11
2

First things first, I don't see how programming to interfaces addresses the expression problem.

In short, the expression problem states that you can only add new data or new functions to a datatype (without recompiling) - not both.

Programming to most interfaces lets you make new functions that use that interface, but you can't add data to the interface without recompiling it.

Now, on to the meat of the question:

Is 'design with types first' ultimately the same as 'design with interfaces first'?

Not quite.

Design interfaces first implies you approach the design from how it's going to be used, rather than how it's going to be implemented.

Design types first implies that as well, but in a language like scala some of the implementation details will impact how it can be structured. Are you going to use structural types? Are you going to use case classes? Are you going to mix in some traits? How you slice the reusable parts will change how you present the interface for the code's use.

1
  • Thanks for your clarification on the expression problem - that's helpful.
    – hawkeye
    Commented Sep 5, 2014 at 0:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.