4 ms·
I think this is a rather simplified and naïve analysis. Getting functional programs and functional APIs to compose well with each other is just as much a challe
by hellofunk 6y ago
I think this is a rather simplified and naïve analysis. Getting functional programs and functional APIs to compose well with each other is just as much a challenge as in other language paradigms. Just because the logic is organized as functions doesn’t magically make things fit together. Your APIs need to speak in a consistent way as well, and the arrangement of your data needs to be the same or easily convertible between your “Lego pieces“. Having spent many years of my career writing functional code all day, it is just as easy to make a mess of things in functional programs as it is an object oriented programs. I do not believe either is inherently better at creating the “Lego“–style.
- willtim 6y agoIt has to be pure functional programming to get the full benefits of compositionality. Most mainstream "functional programming" is not necessarily pure and side-effects are not controlled/tracked. Analogous to how "mostly secure" is not secure, "mostly functional" does not get the full benefits. Pure functional programming is complex, one has to compose effectful functions differently to pure functions. But the pieces do really fit like Lego bricks, especially when the same mathematical abstractions are used consistently. The Haskell community has been extremely effective at creating such consistency, by promoting various abstractions using category theory as a guide. The object-oriented community is not so different in this regard with their promotion of "patterns". I've been writing Haskell professionally for 8 years. The problems with Haskell are the tooling, language stability, the learning curve and the difficulty of reasoning about performance, especially space usage. But composition and re-use works.
- hellofunk 6y agoI'm sorry but this is a lot of shallow hyperbole. It doesn't matter if your functions are pure if they return a unique data type for your API or library that is not understood easily by the caller. So the solution is to make that data easily convertible or generic.. and guess what? That's no different than doing the same in an OO language. In the end, lego pieces are ultimately about the data itself, not how it is manipulated. The key ingredient in making software reusable and portable is the talent and experience of the engineer, regardless of language.
- willtim 6y ago> It doesn't matter if your functions are pure if they return a unique data type for your API or library that is not understood easily by the caller. If it's an abstract data type, it doesn't have to be understood by the caller, it just has to be passed to another "lego brick" which understands the abstract interface. If it's a structured data-type, then there's no reason why it shouldn't be understood by the caller, the type should tell you how to consume it. I do not understand your point. > So the solution is to make that data easily convertible or generic.. and guess what? That's no different than doing the same in an OO language I guess you mean structured data types here? But you have missed my point completely about side-effects, it is side-effects (coupling via back-channels) that prevent composition, in general, in an OO language. OO languages also typically have an obsession with nominal types that can impede reuse. > The key ingredient in making software reusable and portable is the talent and experience of the engineer, regardless of language. You appear to be suggesting that languages/tools don't matter? Why not aspire towards languages that encourage safe composition and re-usable software? Your argument reduces down to "good motorcyclists don't need helmets".
- hellofunk 6y agoAll of your points make great sense in theory, but in practice it rarely works out so smoothly. > If it's an abstract data type, it doesn't have to be understood by the caller, it just has to be passed to another "lego brick" which understands the abstract interface. If it's a structured data-type, then there's no reason why it shouldn't be understood by the caller, the type should tell you how to consume it. And this is exactly how any well constructed OO API works as well. You can have really good and really bad APIs in any language, it really is up to the skill of the developers. I firmly believe that, there’s no language or tool that suddenly makes you create better things. I have in my jobs interacted with high profile robust APIs in C++ as well as functional languages. They can be a joy to use when designed well, regardless of language.
- crimsonalucard5 6y ago>I firmly believe that, there’s no language or tool that suddenly makes you create better things. This belief makes no logical sense. I can easily define a language for you or a tool that can prevent you from EVER making a good thing and I can easily define a tool that can ONLY allow you to make one good thing. For example a programming language that always compiles into a program that does nothing Or A program that always compiles into a performant news website. Ludicrous examples, I know, but important nonetheless. Why are they important? Because these tools illustrate the existence of extremes. If extremes exist then the entire domain must be a spectrum of some sort. Logically, Within this spectrum there are tools that can very much almost magically help you make great things and tools that do the exact opposite. By logic All the tools we have must currently live somewhere on this spectrum. It's just that the problem is so broad we can't prove exactly where on the spectrum each of these tools actually lives... hence debate. However the existence of extremes proves that a spectrum exists and all tools must exist somewhere on the spectrum and some tools must be better than others.
- deleted 6y ago[deleted]
- crimsonalucard5 6y ago> Just because the logic is organized as functions doesn’t magically make things fit together. This is where you're a bit off. You're not technically wrong though. A function that outputs a string can't compose with a function that takes an int as a parameter, but this isn't what I'm referring to. Allow me to illustrate a shorter example. Before we start off, we should both be in agreement that piping results from unix programs into other unix programs is a very lego-like method of programming, so if I can bend a program into following a similar style I've essentially "bent" the program into something that is "lego-like." Say my programming task is to find out the value of (2 + 1) * 3 * 2. I will illustrate how to do this using a very typical functional style then using the point free style then using OOP and finally using unix pipes. The goal in every example is to promote reuse-ability to the maximum potential. With these examples you can develop the insight needed to see why functional programming is more lego like than other forms of programming. functional style: f :: int -> int f x = (x + 1) * 3 * 2 main = print (f 2) The above code is your typical functional program. Coded in a way that's not apparently re-useable at first. However, I look at this function f and I realize I can easily begin refactoring it piece by piece into something more re-useable / lego-like. Take "* 2" let's extract that first into a function called g: g :: int -> int g x = x * 2 f :: int -> int f x = g ((x + 1) * 3) main = print (f 2) Then see "* 3" it's very easy to see how you can extract that into another function called d: g :: int -> int g x = x * 2 d :: int -> int d x = x * 3 f :: int -> int f x = d (g (x + 1)) main = print (f 2) Then see "+ 1". Again easily extracted into a new function called m. g :: int -> int g x = x * 2 d :: int -> int d x = x * 3 m :: int -> int m x = x + 1 f :: int -> int f x = d (g (m x)) main = print (f 2) Pretty re-useable right? Let's rewrite the function "f" so that this becomes more apparent using the point free style: g :: int -> int g x = x * 2 d :: int -> int d x = x * 3 m :: int -> int m x = x + 1 f :: int -> int f = d . g . m main = print (f 2) Note that now it becomes obvious. The function f is a composition of d and g and m. It fits to together like legos. Now let us consider the same thing but with unix pipes. How would that work? Imagine you have three programs each one takes an input from stdin does a mathematical operation in them and throws the result to stdout. Each of these programs is named after the functions shown above and given the same mathematical operations as defined above: g, d, and m. What does the unix expression look like? $> echo 2 | m | g | d So you see I've given you a very primitive example using very basic mathematical operations to show you how easily it is to convert functional code into unix-like code which in turn is very lego-like. I chose a simple example and even then you may not realize it now but if your program follows the rules of functional programming even when it scales it will have the properties that I just illustrated to you. Now let's talk about OOP. The primitive and canonical solution to OOP cannot be decomposed into anything that is re-useable without massive changes to the structure of the code. Even for the simplest cases. Allow me again to elucidate by converting g, d, and m into the canonical OOP code: class F: def __ init__(self, x) self.x = x def addOne(self) self.x += 1 def mulTwo(self) self.x *= 2 def mulThree(self) self.x *= 3 f = F(2) f.addOne() f.mulTwo() f.mulThree() print (f.x) The act of mutation ties methods to context and makes it so methods cannot be reused outside of a context. None of those methods can be reused outside of the context of F. If you want to make OOP into lego building blocks you're going to have to divide things up into smaller classes. Which is not nearly as straightforward as the functional program above. Ultimately you'll realize that you're going to be dividing your class into smaller classes and the context becomes less and less relevant until you have something isomorphic to functional programming. Let's try making the above re-useable. Let's try taking out mulTwo and making that reuseable. class F: def __ init__(self, x) self.x = x def addOne(self) self.x += 1 def mulThree(self) self.x *= 3 class G: def __ init__(self, x) self.x = x def mulTwo(self) self.x *= 2 f = F(g) f.addOne() f.mulThree() g = G(f.x) g.mulTwo() print(g.x) Now for mulThree and addOne: class F: def __ init__(self, x) self.x = x def addOne(self) self.x += 1 class G: def __ init__(self, x) self.x = x def mulTwo(self) self.x *= 2 class D: def __ init__(self, x) self.x = x def mulThree(self) self.x *= 2 f = F(g) f.addOne() g = G(f.x) g.mulTwo() d = D(g.x) d.mulThree() print(d.x) You will note that I'm reusing the constructor, How can that be factored out? Inheritance. class Base: def __ init__(self, x) self.x = x class F(Base): def addOne(self) self.x += 1 class G(Base): def mulTwo(self) self.x *= 2 class D(Base): def mulThree(self) self.x *= 2 f = F(g) f.addOne() g = G(f.x) g.mulTwo() d = D(g.x) d.mulThree() print(d.x) So let's look at things from a birds eye view. With the functional style I was able to refactor to 3 primitives all of which can be reused and composed. OOP I was able to refactor it into 4 primitives with one primitive (Base) ultimately being useless outside of the context of this problem and 3 other primitives that rely on base as a parent. Additionally you will note that I need 6 lines of code to achieve the computation I need and the compositions hardly look lego like because they involve initializations and mutations. Literally for even a simple simple example OOP breaks down. You cannot make it lego-like WITHOUT using functional concepts. Literally the next intuitive step if you were doing OOP is to write this: class F: def addOne(x): return x + 1 Which is heading in a more functional direction as the class just becomes useless sugar around a method that ignores internal state and is basically a function. Also let's keep this discussion civil. I spent a lot of time writing this out for you. I will read and respond to your response but if I read anything like "You're niave" or "your opinions are shallow" you can guarantee I'm not going to continue any form of discussion with you.