I enthusiastically agree with your approach. After two decades of experience and years of OO I have come to prefer the data-first, bottom-up, single-atom approach as utter simplicity and sheer pleasure. OO, and especially inheritance itself, tends to foster undue complexity. And to boot your domain is boardgames: epic win.
Great presentation, but I have a few caveats/criticisms... I think he's got some very good programming techniques going and I like the way he's describing functional programming and bottom up design, but he seems to keep assuming that OO == inheritance. So there's some straw manning going on here. He says "objects do not compose" and starts talking about the evils of deep inheritance, which I agree. I don't use a lot of inheritance in my code. I use composition. Composing is literally in the name. Most of the things he is talking about I do easily in an OO style. I completely dig composing functions and transforming data as a programming style especially in languages like clojure and elixir that give you ways to manage state when you need it and don't have objects. I do feel there are some benefits to building a program via modeling and transforming data w/ functions. But his criticism have less to do with the evils of OO and more the evils of inheritance and Top Level Design/Big Design Up Front. You can easily do Bottom up Design with OO. I might start with a single Game class and start breaking things out and carving out objects and their functionality as Game gets too big and you need things like TileSprite or Character or Item to mention a few classes that evolved over time in my game. And I would do the same in a functional language but instead of classes/objects I would have modules with functions that operate on specific data structures. One could make the mistake of Top Level Design in a functional language/style by trying to build out all the modules, data structures and their relationships ahead of time. Furthermore he seems to criticize object graphs/relationships because "everything is connected." I think he's implying the problems with tight coupling between lots of different objects/classes which can of course happen. But even with no objects, you can have tight coupling between functions and modules. If you look at how all his functions are composed of other functions he had already written, this is coupling. You can't escape different "things" being coupled to other things, whether thats functions, data structures or objects. Whatever approach you go with, you try to limit this coupling as much as possible to just when you really need it and rely on abstractions that let you swap things out later on. This might be easier to do in functional languages/approaches, but he didn't illustrate how. I do get his criticism of "is a" vs. "has a" but realistically most relationships are better modeled as a "has a" relationship (composition) and the "is a" / "has a" idiom is IMHO just a good way to teach new OO programmers or students when to use which and how to model. You can ignore this approach if its not helpful/educational and easily use bottom up design to model objects iteratively as you code. At least, that's what I do. Again, not knocking functional style as it has a lot of benefits over OO but the issues with OO he mentions has more to do with the top level design approach, not OO itself.
Thanks Mark, I really enjoyed this presentation, as clearly others have as I found it via their links and recommendations. There is so much that is compelling in your approach, BUT even as someone who has dabbled significantly in Clojure, I find much of the code hard to read and understand. The reason, I suppose, is that I don't know what "type" things are when they are being transformed by the various function. When writing the code, of course all that meta-information is in your head, but what about reviewing the code six months later? Were I to use this approach in a project -- and I really want to -- I think I would do two things to aid code understanding. One: literate programming so that a high level natural language description of all the parts is baked in. Two: more low-level but descriptive functions on the big "state" data blob that give access to, or transform, the smaller parts. I don't really want to see heavy use of update-in in the top-level functions. Thanks again.
Fantastic presentation! I have exclusively used OO langauges and talks like these provide a refreshing perspective. However, with this approach, we end up with a large number of functions, most of which are not going to be used by the end user (the api user?). If we hide the support functions and hide the state and hide support functions, haven't we backed into object oriented programming again?
+Shahbaz Chaudhary Thanks, glad you enjoyed it! A few points: If you feel that some of the functions are too specific for general use you can make them private to reduce the public API size. You need not hide the state as there is none (no mutable state - it should be all values all the way down). Functions are composable (from a mathematical standpoint) and so have very high reuse (unlike objects). They also are decoupled from objects so work on any value with similar structure. Hope that helps.
If you're hiding state or sharing it, you're not programming well, and you certainly aren't programming in a functional manner. None of Mark's code does either of those things. Functions are not code blocks, it's a terrible legacy of computer science which has muddled the term and that leads people confusing them. Functions are _maps_ or _transforms:_ for some input _x_ the function _f_ maps _x_ from some domain _A_ to _x'_ in codomain _A'_ (which may in fact be the same domain, e.g. addition on the integers is a function on _Z_ to _Z_ ) In programming, your functions should be simply transforms on inputs (data) which produce some output (data). No hidden nonsense, no magic, completely transparent and reasonable. Types have nothing to do with it.
I started to listen to the talk, and am hoping to finish. It seems that Mark is equating bottom-up design with data-first design. I first heard of bottom-up design from a Paul Graham essay, and my impression is that he wasn't speaking of data-first, but more of creating a domain specific language (using lisp macros) to fit the program's solution. If I'm right about this, I don't think data-first, and bottom-up are equivalent. Perhaps Mark has a different perspective here. Perhaps Mark addresses this, and I need to finish listening to the talk. What are other people's thoughts?
+Frank Henard Right, not quite the same, but related. As I discussed in my intro, the more I prepped the talk I think the focus ended up being more on data-first than API-first design (Where API-first is often in the form of objectifying a top-down decomposition).
I prefer operations-first design. 1. Identify the main operations of a given domain. 2. Identify the data (i.e. parameters and result) required for each operation to succeed
I enthusiastically agree with your approach. After two decades of experience and years of OO I have come to prefer the data-first, bottom-up, single-atom approach as utter simplicity and sheer pleasure. OO, and especially inheritance itself, tends to foster undue complexity. And to boot your domain is boardgames: epic win.
Great presentation, but I have a few caveats/criticisms...
I think he's got some very good programming techniques going and I like the way he's describing functional programming and bottom up design, but he seems to keep assuming that OO == inheritance. So there's some straw manning going on here. He says "objects do not compose" and starts talking about the evils of deep inheritance, which I agree. I don't use a lot of inheritance in my code. I use composition. Composing is literally in the name. Most of the things he is talking about I do easily in an OO style.
I completely dig composing functions and transforming data as a programming style especially in languages like clojure and elixir that give you ways to manage state when you need it and don't have objects. I do feel there are some benefits to building a program via modeling and transforming data w/ functions. But his criticism have less to do with the evils of OO and more the evils of inheritance and Top Level Design/Big Design Up Front. You can easily do Bottom up Design with OO. I might start with a single Game class and start breaking things out and carving out objects and their functionality as Game gets too big and you need things like TileSprite or Character or Item to mention a few classes that evolved over time in my game. And I would do the same in a functional language but instead of classes/objects I would have modules with functions that operate on specific data structures. One could make the mistake of Top Level Design in a functional language/style by trying to build out all the modules, data structures and their relationships ahead of time.
Furthermore he seems to criticize object graphs/relationships because "everything is connected." I think he's implying the problems with tight coupling between lots of different objects/classes which can of course happen. But even with no objects, you can have tight coupling between functions and modules. If you look at how all his functions are composed of other functions he had already written, this is coupling. You can't escape different "things" being coupled to other things, whether thats functions, data structures or objects. Whatever approach you go with, you try to limit this coupling as much as possible to just when you really need it and rely on abstractions that let you swap things out later on. This might be easier to do in functional languages/approaches, but he didn't illustrate how.
I do get his criticism of "is a" vs. "has a" but realistically most relationships are better modeled as a "has a" relationship (composition) and the "is a" / "has a" idiom is IMHO just a good way to teach new OO programmers or students when to use which and how to model. You can ignore this approach if its not helpful/educational and easily use bottom up design to model objects iteratively as you code. At least, that's what I do.
Again, not knocking functional style as it has a lot of benefits over OO but the issues with OO he mentions has more to do with the top level design approach, not OO itself.
Thanks Mark, I really enjoyed this presentation, as clearly others have as I found it via their links and recommendations.
There is so much that is compelling in your approach, BUT even as someone who has dabbled significantly in Clojure, I find much of the code hard to read and understand. The reason, I suppose, is that I don't know what "type" things are when they are being transformed by the various function. When writing the code, of course all that meta-information is in your head, but what about reviewing the code six months later?
Were I to use this approach in a project -- and I really want to -- I think I would do two things to aid code understanding. One: literate programming so that a high level natural language description of all the parts is baked in. Two: more low-level but descriptive functions on the big "state" data blob that give access to, or transform, the smaller parts. I don't really want to see heavy use of update-in in the top-level functions.
Thanks again.
Fantastic presentation! I have exclusively used OO langauges and talks like these provide a refreshing perspective. However, with this approach, we end up with a large number of functions, most of which are not going to be used by the end user (the api user?). If we hide the support functions and hide the state and hide support functions, haven't we backed into object oriented programming again?
+Shahbaz Chaudhary Thanks, glad you enjoyed it! A few points: If you feel that some of the functions are too specific for general use you can make them private to reduce the public API size. You need not hide the state as there is none (no mutable state - it should be all values all the way down). Functions are composable (from a mathematical standpoint) and so have very high reuse (unlike objects). They also are decoupled from objects so work on any value with similar structure. Hope that helps.
If you're hiding state or sharing it, you're not programming well, and you certainly aren't programming in a functional manner. None of Mark's code does either of those things.
Functions are not code blocks, it's a terrible legacy of computer science which has muddled the term and that leads people confusing them. Functions are _maps_ or _transforms:_ for some input _x_ the function _f_ maps _x_ from some domain _A_ to _x'_ in codomain _A'_ (which may in fact be the same domain, e.g. addition on the integers is a function on _Z_ to _Z_ )
In programming, your functions should be simply transforms on inputs (data) which produce some output (data). No hidden nonsense, no magic, completely transparent and reasonable. Types have nothing to do with it.
I started to listen to the talk, and am hoping to finish. It seems that Mark is equating bottom-up design with data-first design. I first heard of bottom-up design from a Paul Graham essay, and my impression is that he wasn't speaking of data-first, but more of creating a domain specific language (using lisp macros) to fit the program's solution. If I'm right about this, I don't think data-first, and bottom-up are equivalent. Perhaps Mark has a different perspective here. Perhaps Mark addresses this, and I need to finish listening to the talk. What are other people's thoughts?
+Frank Henard Right, not quite the same, but related. As I discussed in my intro, the more I prepped the talk I think the focus ended up being more on data-first than API-first design (Where API-first is often in the form of objectifying a top-down decomposition).
Homoiconicity!?! DSL's are data first! :)
Clojure is solid for bottom up design. But other lisps are as well. Some of which run on the JVM.
I prefer operations-first design.
1. Identify the main operations of a given domain.
2. Identify the data (i.e. parameters and result) required for each operation to succeed
Very useful
👍🏻