Question

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).

Was it helpful?

Solution

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

OTHER TIPS

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...

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).

Licensed under: CC-BY-SA with attribution
scroll top