Hacker News new | past | comments | ask | show | jobs | submit login

I wish he gave more examples in that response because I'm generally confused what he's talking about. I'm familiar with Racket and F#, but not having used Clojure, I'm missing some context about the Clojure ways of doing things and examples of the problems he claims.

> I feel about them the way I do about switch statements - they're brittle and inextensible.

That is not the case in a language like F# or OCaml. I do note that F# was introduced only slightly before Clojure was, but pattern matching provides nicely extensible functions and are anything but brittle in those languages. Also, active patterns in F# allow one to extend the pattern matching functionality.

> The binding aspect is used to rename structure components because the language throws the names away, presuming the types are enough. Again, redundantly, all over the app, with plenty of opportunity for error.

I'm not sure what he means here. Again in a language like F#, names of the data aren't thrown away. They are pattern matched against, only being "thrown away" to do actual calculations. Nothing is ever lost where the data came from. For example:

    type Shape =
        | Circle r
        | Square s

    let area shape =
        match shape with
        | Circle r -> System.Math.PI * r * r
        | Square s -> s * s
There's no confusion here. In fact, pattern matching in a language like F# allows one to completely remove the possibility of error. For example, this really shows off in parsing applications. Once your parsing function returns a type that can be pattern matched, it's extremely difficult to have an error in the pattern matching sections of code. These are typically the most robust parts of the application.

> I'd much rather use (defrecord Color [r g b a]) and end up with maps with named parts than do color of

    real*real*real*real,
> and have to rename the parts in patterns matches everywhere (was it rgba or argb?)

I don't understand this either. In F#:

    type Color = { R: float; G: float; B: float }

    let colorFunction { R=r; G=g; B=b } = r * g * b
No names are thrown away. Also, the comment on rather using maps seems to assume the data type for every element of the data structure is the same. How do you just use maps when the underlying types of your record aren't the same?



> I feel about them the way I do about switch statements - they're brittle and inextensible.

What is meant by this statement is that pattern matching violates the open/closed principal. If you add a new type to switch on, you need to update all the pattern matching code in the whole application to account for the new type.

It’s one of the two sides of the “expression problem”[0] (the other being object oriented polymorphism).

Clojure’s approach to this is to use “multi methods” which is sort of a “pattern matching”/“strategy pattern”. You are free to add in a new implementation of the multi method without having to update existing code

Here is a great post that talks about Clojure’s approach to polymorphism and covers multi methods in detail: https://aphyr.com/posts/352-clojure-from-the-ground-up-polym...

[0] http://wiki.c2.com/?ExpressionProblem


I think the artificial shape example does not adequately show how variant types/pattern matching are actually used in languages like SML/F#/Haskell. It combines two different cases.

Suppose you have a type `colour` defined as `RGB(int, int, int) | HSL(int, int, int)` and then you add representation as CIE. Then having to update each match on a colour is absolutely a feature not a bug. If you miss some out then your code will be wrong.

On the other hand, suppose you have various ways of serialization (JSON/XML/s-expressions). In this case, it would probably be nice if you could add a way to serialize to e.g. protobufs without having to jump around your codebase and all its clients fixing type errors. But in most languages of the kind we're discussing, you can do! You just have to represent the different serialization methods in some way other than a variant type. For instance, in OCaml you could just use classes and inheritance (although in practice you probably wouldn't because the language provides nicer tools).


Thanks for the link to Clojure's polymorphism. I'll need to read through it later and in more detail.

Isn't the open/closed principle more of an OOP design concept? In a statically typed language like F#, I want and expect to be notified what functions I need to update when I add a new type constructor to an existing type. This isn't a problem and is welcomed. Just because one updates functions doesn't make them brittle or inextensible. By only adding a new pattern matching branch, one is able to extend a function without affecting the other branches. However, this is getting into the statically typed nature of F#.

I think that link explains the expression problem rather superficially. It says you just need to add a new class, but neglects to mention that that also entails adding the new method overrides. So simply saying the OOP way is easy and functional programming is difficult when adding a new type is not really accurate. Same thing for adding a function in the functional programming paradigm, because it neglects you need to add a branch for all types. In reality, OOP inheritance and functional pattern matching are simply transposes of each other, and I'd argue that one is not really necessarily better or worse than the other. They're simply different organizational methods of how the data and functions on the data are organized.


I think Rich's point may be specific to Java switch statements and their limitations. (I know he's familiar with MLs, but I would imagine his frame of reference is mostly colored by Java's control structures.) The idiomatic clojure for your color example would leverage names in the same way:

    (defn color-function [{:keys [r g b]}]
      (* r g b))




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: