54

In a blog post on F# for fun and profit, it says:

In a functional design, it is very important to separate behavior from data. The data types are simple and "dumb". And then separately, you have a number of functions that act on those data types.

This is the exact opposite of an object-oriented design, where behavior and data are meant to be combined. After all, that's exactly what a class is. In a truly object-oriented design in fact, you should have nothing but behavior -- the data is private and can only be accessed via methods.

In fact, in OOD, not having enough behavior around a data type is considered a Bad Thing, and even has a name: the "anemic domain model".

Given that in C# we seem to keep borrowing from F#, and trying to write more functional-style code; how come we're not borrowing the idea of separating data/behavior, and even consider it bad? Is it simply that the definition doesn't with with OOP, or is there a concrete reason that it's bad in C# that for some reason doesn't apply in F# (and in fact, is reversed)?

(Note: I'm specifically interested in the differences in C#/F# that could change the opinion of what is good/bad, rather than individuals that may disagree with either opinion in the blog post).

10
  • 1
    Hi Dan! Your aditude is inspirational. Besides the .NET(and haskell) platform I encourage you to look at scala. Debashish ghosh has written a couple of blogs about domain modeling with functional tools, it was insightful for me, hopefully for you too, here you go: debasishg.blogspot.com/2012/01/… Commented Aug 8, 2013 at 10:44
  • 4
    I was sent an interesting blog post by a colleague today: blog.inf.ed.ac.uk/sapm/2014/02/04/… It seems that people are starting to challenge the idea that anaemic domain models are outright bad; which I think might be a good thing! Commented Apr 2, 2014 at 16:58
  • 1
    The blog post you reference is based on a mistaken idea: "It is normally fine for the data to be exposed without being encapsulated. The data is immutable, so it can't get "damaged" by a misbehaving function." Even immutable types have invariants that need to be preserved, and that requires hiding data and controlling how it can be created. For example, you can't expose the implementation of an immutable red-black tree, because then someone could create a tree consisting of only red nodes.
    – Doval
    Commented Mar 16, 2015 at 15:06
  • 6
    @Doval to be fair that's like saying you can't expose a file system writer because someone might fill up your disk. Someone creating a tree of only red nodes does absolutely no damage to the red-black tree they were cloned from, nor any code throughout the system which happens to be using that well formed instance. If you write code that actively creates new instances of garbage or does dangerous things, immutability won't save you, but it will save others from you. Hiding implementation won't stop people from writing nonsense code that ends up dividing by zero. Commented Mar 16, 2015 at 15:14
  • 2
    @JimmyHoffa I agree, but that has nothing to do with what I criticized. The author claims that it's normally fine for data to be exposed because it's immutable, and I'm saying that immutability doesn't magically remove the need to hide implementation details.
    – Doval
    Commented Mar 16, 2015 at 16:15

3 Answers 3

43

The main reason FP aims for this and C# OOP does not is that in FP the focus is on referential transparency; that is, data goes into a function and data comes out, but the original data is not changed.

In C# OOP there's a concept of delegation of responsibility where you delegate an object's management to it, and therefore you want it to change its own internals.

In FP you never want to change the values in an object, therefore having your functions embedded in your object doesn't make sense.

Further in FP you have higher kinded polymorphism allowing your functions to be far more generalized than C# OOP allows. In this way you may write a function that works for any a, and therefore having it embedded in a block of data doesn't make sense; that would tightly couple the method so that it only works with that particular kind of a. Behaviour like that is all well and common in C# OOP because you don't have the ability to abstract functions so generally anyway, but in FP it's a tradeoff.

The biggest problem I've seen in anemic domain models in C# OOP is that you end up with duplicate code because you have DTO x, and 4 different functions that commits activity f to DTO x because 4 different people didn't see the other implementation. When you put the method directly on DTO x, then those 4 people all see the implementation of f and reuse it.

Anemic data models in C# OOP hinder code reuse, but this isn't the case in FP because a single function is generalized across so many different types that you get greater code reuse since that function is usable in so many more scenarios than a function you would write for a single DTO in C#.


As pointed out in comments, type inference is one of the benefits FP relies on to allow such significant polymorphism, and specifically you can trace this back to the Hindley Milner type system with Algorithm W type inference; such type inference in the C# OOP type system was avoided because the compilation time when constraint-based inference is added becomes extremely long due to the exhaustive search necessary, details here: https://stackoverflow.com/questions/3968834/generics-why-cant-the-compiler-infer-the-type-arguments-in-this-case

7
  • What feature(s) in F# make it easier to write this sort of reusable code? Why can't code in C# be as reusable? (I think I saw the ability to have methods take arguments with specific properties, without needing an interface; which I guess would be a key one?) Commented Jun 28, 2013 at 17:27
  • 7
    @DannyTuppeny honestly F# is a poor example for comparison, it's just a slightly dressed up C#; it's a mutable imperative language just like C#, it has a few FP facilities that C# doesn't have but not much. Look to haskell to see where FP really stands out and things like this become much more possible due to type classes as well as generic ADTs Commented Jun 28, 2013 at 18:16
  • @MattFenwick I'm referring explicitly to C# in that because that's what the poster was asking about. Where I refer to OOP throughout my answer here I mean C# OOP, I'll edit to clarify. Commented Jun 28, 2013 at 18:17
  • 2
    A common feature among functional languages allowing this type of reuse is dynamic or inferred typing. While the language itself may use well-defined data types, the typical function doesn't care what data is, as long as the operations (other functions or arithmetic) being performed on them are valid. This is available in OO paradigms as well (Go, for instance, has implicit interface implementation allowing an object to be a Duck, because it can Fly, Swim and Quack, without the object having been explicitly declared as being a Duck) but it is pretty much a requisite of functional programming.
    – KeithS
    Commented Jun 28, 2013 at 18:17
  • 4
    @KeithS By value based overloading in Haskell I think you mean pattern matching. Haskell's ability to have multiple top level functions of the same name with different patterns desugars immediately to 1 toplevel function + a pattern match. Commented Jul 5, 2013 at 11:44
6

Why is an anemic domain model considered bad in C#/OOP, but very important in F#/FP?

Your question has a big problem that will limit the utility of the answers you get: you are implying/assuming that F# and FP are similar. FP is a huge family of languages including symbolic term rewriting, dynamic and static. Even among statically-typed FP languages there are many different technologies for expressing domain models such as higher-order modules in OCaml and SML (that don't exist in F#). F# is one of these functional languages but it is particularly notable for being lean and, in particular, does not provide either higher-order modules or higher-kinded types.

In fact, I could not begin to tell you how domain models are expressed in FP. The other answer here talks very specifically about how it is done in Haskell and is not at all applicable to Lisp (the mother of all FP languages), the ML family of languages or any other functional languages.

how come we're not borrowing the idea of separating data/behavior, and even consider it bad?

Generics might be considered a way of separating data and behaviour. Generics come from the ML family of functional programming languages are are not part of OOP. C# has generics, of course. So one could argue that C# is slowly borrowing the idea of separating data and behaviour.

Is it simply that the definition doesn't fit with OOP,

I believe OOP is based upon a fundamentally different premise and, consequently, does not give you the tools you need to separate data and behaviour. For all practical purposes you need product and sum datatypes and dispatch over them. In ML this means union and record types and pattern matching.

Check out the example I gave here.

or is there a concrete reason that it's bad in C# that for some reason doesn't apply in F# (and in fact, is reversed)?

Be careful about jumping from OOP to C#. C# is nowhere near as puritanical about OOP as other languages. The .NET Framework is now full of generics, static methods and even lambdas.

(Note: I'm specifically interested in the differences in C#/F# that could change the opinion of what is good/bad, rather than individuals that may disagree with either opinion in the blog post).

The lack of union types and pattern matching in C# makes it almost impossible to do. When all you have is a hammer, everything looks like a nail...

1
  • 3
    ...when all you have is a hammer, OOP folk will create hammer factories ;P +1 for really pinning down the crux of what C# is missing to allow data and behaviour to be fully abstracted from eachother: Union types and pattern matching. Commented Mar 16, 2015 at 15:00
-4

I think in a business application you often don't want to hide data because pattern matching on immutable values is great to ensure that you are covering all possible cases. But if you are implementing complex algorithms or data structures you better hide implementation details turning ADTs (algebraic data types) into ADTs (abstract data types).

1
  • 4
    Could you explain a bit more about how this applies to object oriented programming vs. functional programming?
    – user40980
    Commented Nov 4, 2015 at 22:00

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