9 ms·
Functional Thinking after OO
- SeanDav 16y agoNo doubt functional programming has its place and is very powerful for many types of problems. Just curious, how do you maintain your lovely side-effect free structure while programming a GUI or other heavily event-driven environment? I have a genuine interest in the answers to this as it is one of the things putting me off learning an otherwise very interesting paradigm.
- Swizec 16y agoGUI isn't really the place to do functional programming imho. But check out node.js for the rest of your pain. It is very event driven and can easily be coded completely side-effect free due to closures.
- barrkel 16y agoFunctional programming is a good conceptual fit for data binding in UIs, while functional reactive programming is its application to event streams. I've found the former more practical than the latter, so far.
- jerf 16y agoGUIs themselves aren't the problem; the problem is that all GUI toolkits are written imperatively and since you don't want to rewrite your GUI from scratch you have to end up interfacing your functional program to a huge hunk of imperative code. But it is important to be clear that the problem is actually just the impedance mismatch between the functional code and any huge hunk of imperative code, not special to the problem of GUIs. Functional has some interesting and even good answers to the problem of GUIs, but they all look way klunkier in practice than they "actually are" in some sense because they still have to work through those imperative toolkit bindings. For a clean answer to function + event-driven, see Erlang, which will blow pretty much any other attempt to solve the problems Erlang solves out of the water. Functional doesn't have a problem with event-based, functional blows imperative and OO out of the water at event-based. (Partially because it's a lot easier to write sophisticated runtime like Haskell's or Erlang's that can transparently deal with sync issues that drive imperative programs crazy.) Not enough people are good enough at both OO and functional for this to be commonly understood. You'll know functional languages have entered the mainstream to stay when someone writes a useful functional GUI.
- anghyflawn 16y agoThis is all true, but, in any case, whatever happened to MVC? After all, you can (and arguably should) have your business logic happen in functional and reduce the GUI or whatever (which might or might not be OO) to more or less a thin layer of piping which ships data between the user and the pure parts of the program.
- jamii 16y agoI don't think anyone (sensible) is advocating that the entire program must be free of side effects, just that side effects have a cost in terms of clarity and maintainability and so should be avoided when possible. > programming a GUI or other heavily event-driven environment Nitrogen is a nice event-based GUI library written in erlang. Have a look at the demos here: http://nitrogenproject.com/demos http://nitrogenproject.com/demos
- eru 16y ago> I don't think anyone (sensible) is advocating that the entire program must be free of side effects, just that side effects have a cost in terms of clarity and maintainability and so should be avoided when possible. Indeed. Haskell for example, just `tags' all side-effecting code in the type system. You are free to use lots of side-effects in Haskell. (Even though that would miss the point of the language.)
- eru 16y agoYou might want to look into functional reactive programming, and (unrelated) have a look at how xmonad does the IO-heavy work of a window manager, which is all about affecting external state, in a pure language like Haskell.
- apinstein 16y agoVery interesting article. As a primarily OO programmer that's been playing more and more with functional concepts due to javascript, I definitely see the theoretical advantages mentioned in the article. However, I also see that there are very hard parts to purely functional as well. For instance, to be entirely side-effect free your language has to be able to enforce immutability (which JS lacks). It's also hard (or at least not obvious) how to do certain types of logic flows in a purely functional. For small domain spaces I can visualize how purely functional bottom-up programming effectively causes you to write a DSL for your problem. However I don't have the experience/wisdom to understand if this holds true as the size of the domain increases. Basically, I have concerns that purely functional solutions don't scale (not from a performance perspective, but from an organizational/architectural one). My experience with complex business logic makes me worried that complex new business rules would be difficult to implement in purely functional way and would cause frequent, painful refactoring. I would love to hear from someone that's done a purely functional application with a large surface area that has been subjected to a real-world business environment to comment on the subject.
- francoisdevlin 16y agoFunctional programming does scale very well conceptually, and you've been using it for quite some time. It's just had a different name: Unix Pipes I believe using them to compose several small programs together to get a new solution has been fairly well documented :)
- barrkel 16y agoFinger slip vote on phone, sorry.
- djacobs 16y agoFixed :) But really, isn't it time for a mobile Hacker News stylesheet?
- silentbicycle 16y agoUnix pipes aren't functional programming (which emphasizes immutability and referential transparency) - they're more like the actor model, which creates a system out of lots of small programs communicating via message passing. And yes, they do scale very well, conceptually. Erlang does both, maybe that's where the confusion is coming from. They're very complementary, though: having separate, concurrent processes seems to be an excellent pressure release for difficulties that can build up doing pure functional programming. Also, Erlang has an novel (and extremely effective!) error-handling system for the actors, while error handling for Unix pipelines (or asynch/event-loop systems!) can be tricky.
- colomon 16y agoI'm not quite sure what to make of this article. On the one hand, I completely agree that bottom-up programming is a great approach. On the other hand, I completely disagree that top-down programming is strongly tied to OO programming. At the very least, it certainly is possible to do bottom up programming in OO languages. That's my normal working mode...
- kirbman89 16y agoI agree. I'm guessing that the author doesn't even know what his database tables look like. Starting top-down doesn't make sense. As an OO programmer, it's not best practice to start a project using concrete classes. We should always code with scalability and flexibility in mind.
- tieTYT 16y agoDid you forget to put a sarcasm tag at the end of that?
- deleted 16y ago[deleted]
- tomp 16y agoI think that lightweight dynamic OO languages with first-class functions (JavaScript, Lua) are an interesting middle point. They avoid some rigid features of structural programming inherent to most other OO languages, such as encapsulation and class definitions. Instead, objects serve the role of generic data structures, that can also carry code (behaviour) with them. I feel that a language that would allow both styles to mix even further (immutable objects, pattern matching, tags (for ADTs), multimethods, concurrency) would be really cool to use.
- nickik 16y agoso Common Lisp? :) The Problem with putting everything in a language and supporting everything equally is that people tend to do the wronge thing (like in JS up until the last years) and languages get to big (scala, d, CL). I think a language that tends to FP (and makes it idiomatic) but allow other styles but makes them a bit harder to use. (in a lisp you can always make it look good again if you really know that, that is the right thing to do).
- swift 16y agoRuby belongs in the list you gave as well, I think, but they all have tradeoffs, just in terms of the convenience of using first-class functions in them. Ruby's block syntax (|x,y| x * y) is a lot more convenient than the more verbose "function" syntax of JS and Lua for expressing inline functions (Lua: function (x,y) return x*y end), pretty comparable to something like Haskell. However, Ruby requires you to use either coroutine-style yield syntax or the call() method to actually invoke a function value, while JavaScript and Lua let you write parentheses after the value's name, in the same way that you would call any other function. For first-class functions to be convenient, minimally you need a terse syntax for in-line functions, the ability to call function values as you would normal functions, and closures. (this last, fortunately, all of the above support) I'd argue that language support for currying is also extremely valuable and difficult to live without, because it greatly increases the situations in which a given function is useful. It's frustrating to me that mainstream "scripting" languages like JS, Lua, and Ruby can't get all of these aspects right - I hope for pattern matching and ADTs in a mainstream language, too, but I'm still waiting for a good implementation of first-class functions to arrive!
- djacobs 16y agoMy process is something like this: 1. Start with code centered around pure functions. This is a really good default for your project. It gives you flexibility, completely DRY code, and an easy way to think about concurrency. 2. Store large datasets or shallow objects inside maps. They can be efficient and work really well with functions, especially in languages like Python and Clojure. They're even more useful when, for example, they have a literal dot notation (Javascript) for keys. 3. Sometimes, though, it's just easier to structure data inside of objects. Having a map of maps of maps is fine, but projects can quickly become confusing--at least in my experience. The problem is especially bad if multiple people are coding around maps without any enforced structure or validation on creation. In these cases, objects can make things simple and (often) more unified. When I use objects, though, it's good to avoid mutable state. I think of an object as a handy, unified interface to a map, but with the following extras in a unified way: - A constructor, potentially with sensible defaults - Validations and sensible fallbacks - Data-specific error messages - Methods that are closely related to the data it holds (and always return the object itself, so chaining is simple) In my perspective, building code using functions and organizing it around objects is a nice middle point. It's optimized for both productivity and order, especially if you're careful to avoid state.
- weavejester 16y ago> The problem is especially bad if multiple people are coding around maps without any enforced structure or validation on creation. Using a functional style doesn't mean you can't use constructors, and constructors don't necessarily mean you have to use objects.
- djacobs 16y agoSure, you can have all of the things I listed in FP with some effort. My point is just that OO seems to be good at bringing them all together in a really useful way. (And that way isn't inconsistent with FP.) I aim to strike a balance, to use each style where it is strongest.
- weavejester 16y ago
- joshwa 16y agoI'm curious about what I view as two contradictory points in the article: If I create my own language/syntax layers as I go through the various layers of my program, how does that make it more maintainable? This obviously works for the single programmer, who understands the code he's written (assuming he/she hasn't been away from the code too long), but what happens when you have to maintain someone else's code? If I have to jump into a particular spot on the code to solve a problem, how do I know you've defined <+- as some special monad? As a maintenance programmer, it seems like I'd have to learn a whole new language with each program I have to maintain. (and I, the hypothetical maintenance programmer, am probably not as smart as the original programmer).
- jerf 16y ago"If I create my own language/syntax layers as I go through the various layers of my program, how does that make it more maintainable?" It is isomorphic to "creating an API". It's really more a perspective on the situation than a literal description of what's going on. No matter what you're programing in, you're building up some sort of language that subsequent programmers will have to understand to work on your code.
- scott_s 16y agoThat perspective is, I think, most applicable to Lisp. Because the syntax is so simple, language-level constructs in Lisp appear identical to user-defined constructs - it's all just macros or functions. From the perspective of someone using what you've provided, there's no difference between calling your functions and calling Lisp built-in functions. The ideal is that the application logic will be relatively high-level, only calling these utility functions. Note that this is different from overloading the same name to mean different things depending on context.
- noidi 16y agoFrom On Lisp by Paul Graham (pages 59-60): If your code uses a lot of new utilities, some readers may complain that it is hard to understand. People who are not yet very fluent in Lisp will only be used to reading raw Lisp. In fact, they may not be used to the idea of an extensible language at all. When they look at a program which depends heavily on utilities, it may seem to them that the author has, out of pure eccentricity, decided to write the program in some sort of private language. All these new operators, it might be argued, make the program harder to read. One has to understand them all before being able to read the program. To see why this kind of statement is mistaken, consider the case described on page 41, in which we want to find the nearest bookshops. If you wrote the program using find2, someone could complain that they had to understand the definition of this new utility before they could read your program. Well, suppose you hadn’t used find2. Then, instead of having to understand the definition of find2, the reader would have had to understand the definition of find-books, in which the function of find2 is mixed up with the specific task of finding bookshops. It is no more difficult to understand find2 than find-books. And here we have only used the new utility once. Utilities are meant to be used repeatedly. In a real program, it might be a choice between having to understand find2, and having to understand three or four specialized search routines. Surely the former is easier. So yes, reading a bottom-up program requires one to understand all the new operators defined by the author. But this will nearly always be less work than having to understand all the code that would have been required without them. If people complain that using utilities makes your code hard to read, they probably don’t realize what the code would look like if you hadn’t used them. Bottom-up programming makes what would otherwise be a large program look like a small, simple one. This can give the impression that the program doesn’t do much, and should therefore be easy to read. When inexperienced readers look closer and find that this isn’t so, they react with dismay. We find the same phenomenon in other fields: a well-designed machine may have fewer parts, and yet look more complicated, because it is packed into a smaller space. Bottom-up programs are conceptually denser. It may take an effort to read them, but not as much as it would take if they hadn’t been written that way.
- deleted 16y ago[deleted]
- AndrewHampton 16y agopg quote from the article: you don’t just write your program down toward the language, you also build the language up toward your program. As you’re writing a program you may think "I wish Lisp had such-and-such an operator." So you go and write it. Afterward you realize that using the new operator would simplify the design of another part of the program, and so on. I remember thinking when I first read through pg's essays a few years back and again now that you can do this in declarative languages as well using functions. For example, my programs often looks something like this: main(){ var data = getData(); pushData(data); } function getData(){ var data = fetchDataFromSource(); var errors = validateData(data); if(errors != null) //handle errors return processData(data); } function fetchDataFromSource(){ ... } function validateData(var data){ ... } ... Could anyone explain why functional languages are better for this? It seems to me this is the same as "building the language up to your program".
- jshen 16y agoYou're example is too small for the differences to become clear. There isn't any OO in your code for example.
- jshen 16y agoI can't edit my comment anymore, but I wanted to add some more detail. Building larger programs is about clean abstractions. What your doing only needs basic functions for abstraction, but when you try to do something larger you'll begin to need/want more ways to abstract your code. Then the differences will become clear. But, you punted by not actually handling the errors. As others comments show, your example may need more abstraction tools once you try to handle the errors :)
- joevandyk 16y agofor example, getData() could be written something like: getData = fetchDataFromSource . validateData . ProcessData See http://www.haskell.org/haskellwiki/Function_composition http://www.haskell.org/haskellwiki/Function_composition
- ww520 16y agoThis is off topic. Is there a language that automatically generates code when the missing functions are referenced? It would be good for top-down style and test-driven style programming. E.g. I start with a high level flow of a program. int main() { outputData( processData( getData() ) ); } When compiled, the compiler realizes all the called functions are missing and generates provisional stub code for them, with sensible default for parameters and types. int @getData() { return 0; } int @processData(int param1) { } void @outputData(int param1) { } The program can compile and run. I then go ahead to fill out the content of the functions. The provisional functions are marked as such (the @ marker) where the compiler can run in a validation mode to flag all functions not implemented yet. When a function is done, the provisional marker can be removed. As more function calls are added, each compilation generates more provisional functions, which can be filled out. Also as the completed function's parameters and types become concrete, the compiler can update the provisional caller or callee functions. This kind of help from a compiler can really make test-driven development easy. I can write the tests first that make calls to yet-to-exist functions and the compiler generate the provisional version for me. And the tests run right the way.
- silentbicycle 16y agoIn statically typed languages, that's usually avoided, because a typo in a function name can escalate to subtle bugs. Also, working with partial information complicates the type inference; in your example, at best, it could infer () -> `a for getData, `a -> `b for processData, and `b -> () for outputData. In some languages (such as Smalltalk, Lua, and Ruby), there is an explicit "message not understood" hook that is called when a nonexistent function is referenced, and you can define stub behavior there.
- ww520 16y agoGood point about the function name typo in static type language. Provisional and complete functions are supposed to be different. The compiler can have a validation mode to flag all provisional functions, which would catch the wrong function name intended for a complete function. The "message not understood" hook in dynamic languages is good for runtime handling of missing functions, but I actually want the compiler to generate the source code for the provisional functions, to make exploratory development easier.
- VladRussian 16y agofunctional programming naturally provides for reducing the complexity of the system being modeled by the discovery of common patterns in the data and the algorithms applied. The foundation of OO approach is modeling system as it is with all its complexity. This approach is cheaper, ie. easy on the brain, yet it results in the much more unnecessary complex, bloated, system. The same, structurally, code is multiplied through the system because it is bound to different data. Please spare me lectures about how it is an incorrect approach - you'd insult hundreds, if not thousands, of smart people whose source code i've seen during the last almost 20 years. You can look yourself at any OO project in the open source or at your workplace.