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

Jimmy Hoffa
  • 16,039
  • 3
  • 69
  • 80
Danny Tuppeny
  • 896
  • 6
  • 13
  • "Note: I'm specifically interested in the differences in C#/F# that could change the opinion of what is good/bad" - whose opinion? Note that you used the word "opinion". Do you suffer from being a conformist? Then stop. You are not obligated to write a bunch of oop code in order to use C#. You could use structs when all you need is data, but you must be careful with structs as they get copied by value, and other stuff. Sometimes all you need is a bunch of stateless functions. You have to create a class to encapsulate them, though not in VB.Net. This is just a restriction of the C# language. – Job Aug 05 '13 at 20:03
  • I was interested in other peoples opinions; so I might learn from them. Lots of people will have opinions based on things I hadn't thought of; I'm simply trying to learn. – Danny Tuppeny Aug 06 '13 at 05:52
  • 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: http://debasishg.blogspot.com/2012/01/event-sourcing-akka-fsms-and-functional.html – AndreasScheinert Aug 08 '13 at 10:44
  • "Given that in C# we seem to keep borrowing from F#, and trying to write more functional-style code" Because some things are easier in functional or useful, but I think this implies people want c# to move from oo to functional, which I disagree with. I think its giving he developer a choice to use functional paradigms when its easier, but its to supplement not replace oo. – Andy Mar 18 '14 at 01:29
  • 4
    I was sent an interesting blog post by a colleague today: http://blog.inf.ed.ac.uk/sapm/2014/02/04/the-anaemic-domain-model-is-no-anti-pattern-its-a-solid-design/ 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! – Danny Tuppeny Apr 02 '14 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 Mar 16 '15 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. – Jimmy Hoffa Mar 16 '15 at 15:14
  • 1
    @JimmyHoffa I'm not sure I get your point. You seem to be saying that because there's no way to stop people from writing stupid code, it's OK to do error-prone things. Hiding something's implementation lets you provide guarantees and isolate bugs. There's a big difference between using a set with a hidden implementation and directly manipulating trees as if they were sets. With the former, any set-related bugs can only occur in the implementation. In the latter case you have to make sure every part of the program correctly manipulates the trees (e.g. doesn't insert duplicate elements.) – Doval Mar 16 '15 at 15:44
  • 1
    @Doval my point is hiding implementation gives you no more safety than immutability does, so there's no point in hiding immutable state. No one can do dangerous things *to* it. That's the point of hiding implementation, to stop people from doing dangerous things *to* your implementation, not *with* it. They can do dangerous things *with* the divisor operation... You can't stop people from doing stupid things *with* code, only *to* it. And you can do nothing *to* immutable state. – Jimmy Hoffa Mar 16 '15 at 15:56
  • 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 Mar 16 '15 at 16:15

3 Answers3

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

Jimmy Hoffa
  • 16,039
  • 3
  • 69
  • 80
  • 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?) – Danny Tuppeny Jun 28 '13 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 – Jimmy Hoffa Jun 28 '13 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. – Jimmy Hoffa Jun 28 '13 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 Jun 28 '13 at 18:17
  • Haskell, for instance, doesn't require what it calls "type annotations". These are input/output contracts, similar to the signature of an OO overloaded method, that define what types are valid inputs and expected outputs of a named function. Where declared, they're basically just compiler checks; the actual function is typeless. – KeithS Jun 28 '13 at 18:23
  • Another feature is value-based overloading. You see it both in Haskell (static typing) and Erlang (duck-typing); the implementation of the function that is executed can be determined by the value, not just the type, of the input. Consider the factorial function; in both languages, there would be a function for the base case (0! = 1) and a different one for all other cases (n! = n*(n-1)!) and the runtime chooses which one executes based on a check of the value. – KeithS Jun 28 '13 at 18:31
  • 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. – daniel gratzer Jul 05 '13 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...

J D
  • 2,332
  • 24
  • 17
  • 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. – Jimmy Hoffa Mar 16 '15 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).

  • 4
    Could you explain a bit more about how this applies to object oriented programming vs. functional programming? –  Nov 04 '15 at 22:00