14 ms·
A Year of Functional Programming
- jacquesm 12y ago" Recently I looked at some code I wrote 8 months ago and was shocked! I looked at one file written in “good OO-style”, lots of inheritance and code reuse, and just thought “this is just a monoid and a bunch of crap because I didn't realise this is a monoid” so I rewrote the entire thing to about a third of the code size and ended up with double the flexibility. Shortly after I saw another file and this time thought “these are all just endofunctors,” and lo and behold, rewrote it to about a third of the code size and the final product being both easier to use and more powerful." That would be so much more useful if it came with the examples.
- piokoch 12y agoExactly, 2/3 code reduction sounds so great that I would like to be able to see that.
- dkersten 12y agoI, too, would like to see examples. Though I will note that I recently saw some OO code written by a colleague that takes in a set of data names, runs a set of processes on them (translating the names to process-specific ids, fetching the data, turning it back into generic names) and then outputs it to one or more places. It was a pretty standard class hierarchy for reusing common code, selecting optional processing and so on. I've been spending a lot of time doing functional programming lately and would call myself a little bit of a functional programming enthusiast, so when I looked at the code, my first thought was that each of these parts are just functions that get passed into the appropriate places as higher order functions. Smaller, simpler and more flexible because you can customise what happens just by changing a function. It occurred to me that when I think about problems in a functional programming mindset, there really isn't a need for crazy class hierarchies at all, because they're really just a dispatch mechanism for code reuse, but higher order functions acting on pure data structures handle 90% of this just fine with very little work. The other 10% are the complex cases that FP solves using fancier techniques like pattern matching (in the simpler cases) or multimethods or something similar in the complex cases.
- findjashua 12y ago"there really isn't a need for crazy class hierarchies at all, because they're really just a dispatch mechanism for code reuse, but higher order functions acting on pure data structures handle 90% of this just fine with very little work" You did a better job of putting my thoughts to words than I could.
- bkirwi 12y agoIf you're interested in examples of useful monoids and similar structures, you may want to have a look at the Algebird[0] project or the recent work on CRDTs[1]. The code savings in these projects comes from being able to write the complex distributed aggregation / conflict resolution / etc. code just once, and reuse it with a menagerie of useful implementations. Personally, I like structuring things using these tools when I can get away with it. They're simple and broadly applicable abstractions, and mean I can reuse both my intuitions and my code in wildly diverse situations. [0] https://github.com/twitter/algebird https://github.com/twitter/algebird [1] http://highscalability.com/blog/2010/12/23/paper-crdts-consistency-without-concurrency-control.html http://highscalability.com/blog/2010/12/23/paper-crdts-consi...
- seanmcdirmid 12y agoWhat we want is a comparison: what was the OO code, and what did the functional code turn out to be? That way, we can judge for ourselves. Typically, these examples take low-end crappy OO code and convert it to high-end elegant FP code. But this doesn't really convince anyone.
- samstokes 12y agoI don't know the OP's examples, but I think he is saying something subtly different: that he took (his own) low-end crappy OO code and converted it to high-end elegant OO code, using the insight gained from how he would have written it as FP.
- deleted 12y ago[deleted]
- seanmcdirmid 12y agoI didn't get that from the article, but they aren't explicit about it.
- jacquesm 12y ago
- coolsunglasses 12y agoReal world example: https://github.com/bitemyapp/bloodhound/blob/master/Database/Bloodhound/Types/Class.hs https://github.com/bitemyapp/bloodhound/blob/master/Database... https://github.com/bitemyapp/bloodhound/blob/master/Database/Bloodhound/Types/Instances.hs#L18-L23 https://github.com/bitemyapp/bloodhound/blob/master/Database...
- jimbokun 12y ago1. What does this code do? 2. Where is this previous code this replaced that was 3 times the size?
- coolsunglasses 12y agoI've seen a lot of code that was larger than necessary because the author didn't know what a Monoid or Functor were. Cf. "Visitor pattern" I wasn't ignorant enough at the time I wrote the code to fuck up in the way you describe. I can only show an example of stuff I've made. Here we go, three more uses of Monoidic functions (mempty, mappend) https://github.com/bitemyapp/bloodhound/blob/master/Database/Bloodhound/Client.hs#L174-L178 https://github.com/bitemyapp/bloodhound/blob/master/Database... It's not just about code, it's about conceptual memoization. If you know what a Monoid is and you're learning how a data type like ByteString works, you can intuit how to "stitch" bytestrings together as well as how to get an "empty" ByteString. It's about killing off unnecessarily ad-hoc APIs as much as anything. Being able to realize when you're implementing a Functor/Monoid/Applicative/Monad is powerful because it lends you intuition on what the rules for a well-behaved API are and allow powerful, polymorphic code-reuse. Consider the reusability of the functions here across the vast set of Monadic types out there: http://hackage.haskell.org/package/base-4.7.0.0/docs/Control-Monad.html http://hackage.haskell.org/package/base-4.7.0.0/docs/Control... This is worth pondering as well: https://github.com/jwiegley/simple-conduit https://github.com/jwiegley/simple-conduit The code savings described are usually associated with not having to rewrite the polymorphic generic functions used in the Haskell ecosystem for the one-off a person made. The conceptual power from having a community that understands these patterns is more important. You'll learn more and faster by learning Haskell. https://github.com/bitemyapp/learnhaskell https://github.com/bitemyapp/learnhaskell
- ww520 12y agoSecond design is the better design most of the times. It's not something special about functional programming. The first version is mostly just getting it working and released. Revisiting the code always gives opportunity to refactor, simplify, and re-design, which leads to reduction in complexity, reduction in size, and increase in reuse.
- Kiro 12y agoWould you recommend starting with Scala before Haskell if one want to learn FP?
- szatkus 12y agoNo. Actually it would be easier to learn Haskell without knowing any imperative language at all. For me Scala also was "gateway drug to Haskell" and after using Haskell for some time I got some good FP habits which made my Scala/JS/whatever code better.
- dllthomas 12y agoHaskell made my C code better.
- chriswarbo 12y agoNo. Scala does some interesting things, but it adds a lot of complexity in order to interact with typical JVM code. Haskell doesn't have that kind of baggage, so at its heart it's a very very simple language (Lambda Calculus + algebraic datatypes + typeclasses). There are a bunch of extensions, but they can be mostly ignored while getting to grips with the basics. Programming in Haskell can involve a lot of unfamiliar concepts like monoids, functors, monads, etc. but these are just the APIs used by libraries; the language itself doesn't care about them (except for "do" notation). Just like you don't need to understand design patterns to learn how Java works.
- VLM 12y agoAnd yet that complex JVM interaction makes one possible way to "get toes wet" is to wrap existing lower level java processes (and libraries and things) in a larger functional wrapper. Top down. Assuming you have some experience, confidence, or sample code in java that you can use or understand in the problem domain. So rather than trying to find a way to use recursive functional definition of a factorial in your code (bottom up) it Might be possible to work top down and make the "main loop" or whatever of your java program a functional construct of some sort. I am in no way claiming this is the best way, only way, or even a good way to learn FP concepts but it is a possible way to at least get feet wet, plus or minus your personal characteristics. It is merely an alternative to the extremely popular nearly universally pushed educational strategy of bottom up introduction of FP.
- pron 12y agoI used to like functional programming. Now I think that it's -- more often than not -- a solution in search of a problem. I can understand some of the things FP gets you (although those come at a cost, which I'll get to later); I'm just not so sure these are the things we need, or that FP is the best solution for them. One is code reuse: yes FP code is definitely more reusable. The problem is that the kind of code it enables reuse of more than OO is very small, simple loops. This is never a big issue. Writing the same 4 line method -- which could have been abstracted with a monad -- 10 times in a 50-150K LOC project is never a big problem, and these methods rarely contain bugs. On the other hand, OO code is often easier to refactor and modify. Sometimes -- in Java for example -- dynamic linking combined with OO, lets you modify/add functionality without even re-compiling existing code. Heck, it let's you add and load new polymorphic type implementations at runtime. It is much more malleable than FP code. Another is state management: yes, FP is one solution to the problem of managing state, especially in a multicore environment. But it is neither the only solution, nor is it the best. The Clojure solution of -- for lack of a better name -- "transactional" mutable state is simpler than the pure FP one, and just as safe. I.e. there are ways to make side-effects safe without restricting them so much that they become a nuisance (after all, all software, possibly with the exception of compilers, exists for the sake of its side effects). Finally (and I've said this before on HN), a language like Haskell discounts the very useful choice of reasoning about your code after it runs, favoring, instead, all-upfront reasoning, often at the expense of facilitating the former. There are some domains where figuring everything up front is very important. Others, where trial and error is far more productive. So pure FP increases code reuse, but of code that's not that important to reuse. It helps deal with the hard problem of state management, but other, simpler solutions exist. Finally, it makes it hard to "feel" how your code runs, to debug it, to profile it, and more. I think it is based on the premise that if programming could be made more formal -- more mathematical , if you will -- it will become easier/"better". But that premise has not been shown to be true. The lack of magically bug-free, FP OSs, drivers, control software, and large applications, shows that even the biggest supposed benefits of languages like Haskell, are yet to be demonstrated in the real world. EDIT: Just to clarify: Unfortunately FP does not have a definition, so, when I was saying "FP", I meant "FP as the article's author practices", which means "statically typed, pure FP". FP using Java streams, FP in Clojure/Erlang, and FP in Haskell mean very different things.
- analog31 12y agoAsk HN: As somewhat of an old timer, I have a couple of questions about FP, not really worthy of creating their own thread: 1. I learned a rule (in my Pascal textbook), "avoid globals." Is FP just an embodiment of that principle? 2. I do a lot of programming involving hardware, often homemade. Hardware has state, such as whether a lamp is turned on or off. Does this just mean FP is inappropriate, or are there techniques out there that accrue the benefits of FP in systems that necessarily have state?
- abraxasz 12y agoI'm not a specialist of FP, so please take what I say with a grain of salt. Before answering your questions, here's a preliminary point: 0) The problem is that FP is not really defined anywhere, or rather, everyone has its own definition. I was wondering about the definition of FP about a month ago, and discussed it with my roommate who's a phd student in programming languages. The conclusion of this 2-hour long conversation was: well FP is kind of a fuzzy concept. The concepts of FP languages, typed languages, static languages, etc.. all tend to conflate. Some people will consider that Scheme, Python, Ruby, Scala, Haskell, OCaml, R are all FP languages. Some will say that only Haskell and OCaml really are. Whatever.. Keeping that in mind, here some attempts at answers: 1) In a very loose sense, I guess that you can say that, and to make it possible, you need some constructs in your language, the minimum requirements being higher order functions, and maybe closures. I'd say that's the minimum, but again I'm no specialist. But then if you want to push this "no globals" moto to its logical extreme, you get to a point where you can't even use a "print" function, since it has some side effects. Haskell does, and solves the problem with monads. But to have monads you need a very particular type system, which leads to the question of wether you need a type system to go FP all the way? I don't know. Someone more knowledgeable should answer that 2) I don't know about hardware. But like I said, FP can deal with state. In fact, even hardcore FP as embodied by Haskell can deal with state. It's not trivial to wrap your head around the concept, but it's worth giving it a shot.
- noelwelsh 12y ago1. I think "avoid globals" is a reasonable way into FP, but FP itself involves a lot more than just avoiding coupling via global state. I would say the main components are - nested definitions; - lexical scoping; - first class functions; - static typing; and - a whole bunch of programming techniques (e.g. monads) that have grown around them. 2. You certainly can use FP ideas in low-level programming but you'll have difficulty finding a FP language that targets your hardware. There are a few small Scheme implementations that might work. Rust is viable if we're talking 32-bit CPUs, not 8-bit. (I imagine the Rust developers would not claim Rust is a functional language, but it has absorbed a great many ideas from functional languages. I prefer to talk about modern programming languages instead of functional programming languages. Rust, Haskell, and Scala are all modern and have many features in common. Only Haskell claims to be a pure FP language though.)
- SeanDav 12y agoNot at all trying to diss the OP, who wrote an interesting article, but if anything, this has put me off FP a bit. It really does seem like a lot of effort to go through for unclear benefits. I have no doubt that learning FP will make me a better programmer (so perhaps it is worth it for that alone) but it seems to me I should rather spend those hours learning more data structures, or algorithms or machine learning. Not sure I get it. edit: it appears partially to be a definition problem. Exactly what is meant by FP and in what language, is a large part of the question.
- JackMorgan 12y agoI'm not sure what part of reducing a codebase by 2/3rds but with double flexibility is an "unclear benefit". Imagine if you could do that with a C# or Java library, people would be freaking out. Or getting almost all the safety of unit tests without writing and maintaining unit tests! That's awesome! You want to be a lot better programmer? Go through SICP! It'll cover FP, data structures, interpreters, algorithms, and OO. You think you know OO now? I'd wager the chapter on OO will blow your socks off with awesome stuff you can use right now. And you'll learn FP enough to give you a taste of what's possible in the more powerful languages like Haskell. Rather than sit around trying to figure out if it'll be worth it, just do it. I've never heard a programmer who has learned it who has said it was a waste of time. So then, what are you waiting for? No study will ever prove its better, just like no study proved Java was better, or C++ was better, or C was better. It's impossible to prove. Was each objectively better? In some ways. Is Haskell objectively better than all of them? Yup. There's all the proof you're going to get: opinions of those who know all of them. You either trust that, or you stay comfortable and fall behind.
- mark_l_watson 12y agoNice article! I have been using Clojure for years, initially because a repetitive customer mandated its use, later for my own projects because it reduced my development time and is fun to code in. I tried Scala (really liked Martin Odersky's Coursera class!) but it did not stick. That said, Haskell has started to win my mind share. At least for my own projects I have been mostly using Haskell this year, with some bits of Ruby for quick text processing and munging. When my Haskell abilities improve I would like to start using it for small text processing utilities also. Sometimes Haskell can be as frustrating as hell (yes, cabal, I am thinking of you). It can be frustrating also when writing a bit of useful code took a while because of the never ending (for me) learning curve. However when I am in the flow with Haskell, it feels like my 30+ years of Lisp experiences, but with a stronger language. It has become a cliche about Haskell's type system guiding you away from bugs, but it is true. I never had that feeling with Common Lisp and Scheme (but I have not tried strongly typed Racket yet).
- morkbot 12y agoThat's a bit of an off-topic but may I ask, why Haskell over Clojure. What are the differences? You partly asked that in the 2nd part, but I would like to know more. I never did any serious FP so would love to know where to start: Scala? Clojure(Script)? Haskell? F#?
- bkirwi 12y agoHere's an article that was on the frontpage recently; some reflections from a Clojurist that switched to Haskell: http://bitemyapp.com/posts/2014-04-29-meditations-on-learning-haskell.html http://bitemyapp.com/posts/2014-04-29-meditations-on-learnin...
- mark_l_watson 12y agoIt is mainly the stronger type system. I like the upfront checks. That said, I have used Clojure a lot - a fine language. BTW, I think that learning some Haskell will help with Clojure.
- JackMorgan 12y ago
- virtualwhys 12y agoSuprised that the year ended with one language and not another, particularly given his background. This could be indicative of a "good enough" FP trend where the net affect of language authors cherry picking FP constructs does not, ironically enough, result in significantly increased adoption rates for those FP languages that are at the forefront of FP R&D. Case in point: the recently announced Swift appears to have a decent grab bag of FP candy that will immediately appeal to legions of iThing app developers. In short, it may be that Haskell, and particularly Scala (fending off Java 8, Kotlin, Groovy, and Clojure) will be fighting for scraps until one of them comes up with a killer stack that launches them out of niche status into the mainstream. Could be awhile yet...
- brudgers 12y agoIt's not that I don't think that programming in a functional style isn't a good idea , it's just that the discussions about functional programming and the languages people use to implement its style so often suggest: If you use language 'x', then using mutation is wrong. and recently, I've been thinking about the practically important but socially awkward question: What language is best for implementing mutable state? By which I don't mean: What language makes avoiding mutable state impossible? because given my skill set on some absolute scale that encompasses Knuth, I've got big fish to fry. Trying to maintain ideological purity is a distraction at best and an impediment at worst, if I am ever to see an [m42] and have a solid intuition about its solution. Even the most rousing chorus of 'Onward Christian Soldiers' isn't going to help me implement Hoare's Quicksort in Clojure. Consing and filtering are great, but they miss the idea of working in-place. Racket's built in O(log n)priority queue isn't a substitute for a traditional O(1) queue implementation: +-------+ | | o-|------------------+ +-|-----+ | | | v v +-------+ +-------+ +--------+ | | o-|--->| |o-|--->| | nil| +-|-----+ +-|-----+ +-|------+ | | | v v v 'a 'b 'c The built in priority queue is mutable anyway. The illustration of queues in Racket isn't rendered from whole cloth. If one searches the Racket documentation (depth first is implied?) the only hit for 'queue is the priority queue which lives in the 'data library [and again, it's still mutable and not thread safe]. But the real problem is that providing Racket implementations on Rosetta code is one of those ways Racketeers are encouraged to give back, and the implementation of LIFO on Rosetta Code shows the priority queue, rather than an actual queue, in part because using 'mcons is considered taboo under functional programming mores. It's a socially acceptable answer, rather than a correct one. Some things just have to be to be mutable in order to get built in a way that will be useful when dealing with large tables when the first test of usability is getting built in the first place. It's not that playing with dangerous objects ought to be encouraged, it's that it there's nothing wrong with a little dynamite now and then for removing stumps when the alternative is lifting the grinder to the top of the cliff with a crane. All that really matters is acting in accord with the fact that, yes, we're using dynamite. There's a continuum between Coq and MMIX. A good functional programming language allows refactoring a solution from right to left. It doesn't pretend as if there isn't a right wing or that it doesn't matter politically.
- naland 12y ago>I've been coding since the age of 8, so 26 years now. >I started with BASIC & different types of assembly then >moved on to C, C++, ... So to 68002 he had nine and a half or ten, here the article it's enough for me— even I'm interested in FP, but too old.
- IvarTJ 12y agoHow well does deploying Haskell web applications to a low-memory VPS work? My experience with the Play framework tells me that Scala is out of the question. Presumably using Haskell involves cross-compilation, considering the memory usage of GHC compilation.
- dllthomas 12y agoI've been deploying Haskell web applications to a 1G and a 512M VPS, and both have been working fine. Initially I was able to build on the boxen, but more recent libraries/compiler runs out of memory building some dependencies. Obviously that wasn't ideal anyway since it was taking up resources the live site could be using, although none of the relevant sites are supporting a ton of traffic. Building on my local machine and pushing up the executables has been working just fine, though.
- mncolinlee 12y agoThe one question I wish this article answered is: what specific, quantifiable benefits could I get from using a real functional language RATHER THAN merely reactive extension libraries to an imperative language. The point many programmers are at is far removed from diving into Haskell and hard math in functional languages. Many of us are still learning why to compose observables for multithreading. From a application programmer perspective, what could we gain by diving into FRP completely? If Scala is the gateway drug, then the dealers need to do a better job explaining what the hard stuff actually does for us.
- briantakita 12y ago> Realisation: Abstractions Objects are coarsely grained abstractions. Separating function Modules from the Data allows more opportunity to be DRY, better performance, and more complexity scalability. > Realisation: Confidence. Types vs Tests > In Ruby, that often meant testing it from every angle imaginable, which cost me significant effort and negated the benefit of the language itself being concise. This is largely a cultural artifact from the history of the Ruby & Java communities. We are discovering that is is more useful to have your tests written from a black box perspective (in any language). White Box Testing is less useful and causes the architecture to be locked down. White Box Testing should be a rare occurrence. From my development practices, Static + Strong typing helps with performance & debugging failing tests, but not much else. It imposes rigidity in the architecture & delays my development feedback loop. I know that this is a matter of taste & I will catch much flak for my opinion as it conflicts with other peoples' taste, but it's my truth :-)