7 ms·
When Does Point-Free Notation Make Code More Readable?
- barrkel 9y agoPoint-free notation builds a program indirectly using expressions, rather than declaratively and directly like most programming languages. You need to follow data and control flow in order to understand the program that is built. I think it's rarely easier to read or understand, with a few major exceptions. Certainly toy examples like in the article are too simple to highlight the benefits. If the blocks being composed are reused and recombined a lot, but are meaty enough not to be inlined, then the composition effectively turn into a kind of high-level DSL. Consider e.g. a data processing pipeline using relational operators; filters, joins, sorts, projections etc. Those things have meaty implementations, but fairly simple interfaces; they're tailor-made for composing into larger abstractions that have the same external interfaces. If the problem being solved inherently requires building a custom program to solve it, then it's hard to avoid writing something that isn't isomorphic to point-free style. For example, a reporting tool that doesn't dump the user in a SQL editor.
- Etheryte 9y agoI don't really find the argument convincing. I'd argue that the original approach was more intuitive.
- deleted 9y ago[deleted]
- wellpast 9y ago"Intuitive" is too subject. What we're interested in is simplicity. And this is an objective measure. This (A): partial(Math.max, 2) is objectively simpler than this (B): function atLeastTwo(someNumber) { return Math.max(2, someNumber); } Why is it objective? Because it expresses the same exact value (i.e., function) with an objectively fewer number of concepts. When (A) is unfamiliar it feels less intuitive, but with practice/experience that sense of unintuitive goes away, and then you are left with (B) a simpler expression -- and therefore less cognitive load on the developer trying to make progress.
- pacala 9y agoUsing a good function syntax [Scala], the pointed version is: { x => Math.pow(2, x) } Obvious it's a function of 1 argument. The pointfree variant requires 1 extra concept [partial], plus building a mental map of the arguments, to reconstruct what kind of a function [or value!] the pointfree expression represent. Furthermore, implementing the reverse check is syntactically symmetric in the pointed version: { x => Math.pow(x, 2) } Whereas the pointfree version, assuming the function is not commutative, accumulates even more concepts: partial(flip(Math.pow), 2)
- mrkgnao 9y agoUsing even better (PureScript) syntax, the pointfree version of the latter can be written in notation similar to a hom-functor. f1 = pow 2 f2 = pow _ 2 This makes it look like a lambda but without having to bind an argument explicitly. Makes it much nicer to write complicated expressions (although I can't speak to the readability for people less fluent in the syntax).
- randallsquared 9y agoWell, it isn't like you can avoid understanding B by using A: A adds a concept, but it doesn't remove any of the existing ones; it just makes you do B in your head when you see A to arrive at the concept which is reified in the function name. Even if you're only using it in one place, `atLeastTwo(some_value)` is simpler than `partial(Math.max, 2)(some_value)` in at least that way.
- TorKlingberg 9y agoIt's a functional programming thing.
- freefal67 9y ago"JavaScript doesn’t include this out of the box, but it’s an interesting exercise to write your own." I may be misunderstanding point-free notation (I had never heard of it before), but I think `bind` (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...) let's a programmer provide some of the arguments to a function and returns a new function that requires only the remaining arguments.
- beaconstudios 9y agobind provides partial application but doesn't directly help towards point-free notation.
- Too 9y agoIt's also easy to implement as a one liner var atleastTwo = function(x){return function(){return Math.max(2,x);} Even simpler with typescript var atLeastTwo = (x) => (() => Math.max(2, x))
- WalterSear 9y agoES6 const atLeastTwo = x => Math.max(2,x)
- leshow 9y agohis is a function that returns a function, i.e. you'd need to call atLeastTwo(x)(); to get a result, yours is just a function. in es6 the equivalent thing to what he wrote would be: const atLeastTwo = x => () => Math.max(2, x);
- deleted 9y ago[deleted]
- deleted 9y ago[deleted]
- dreamcompiler 9y agoWhy do we need yet another term for concepts already known as function composition, currying, and partial application?
- Deestan 9y agoIt's not a new term, is the partial answer. The rest is that "point free" describes a programming paradigm, while terms you listed describe the mechanisms you can use to apply it.
- SomeStupidPoint 9y agoIt comes out of the mathematical notion of a point and what not, right? Something, something, only discussing the composition of maps without ever talking about how the initial categories are tied to points. Category theory always makes for weird names for programming features.
- rocqua 9y agoPoint free notation often depends on currying, but it is not the same. Instead, point free notation is about only using composition and currying to define your functions.
- mrkgnao 9y agoPointfree style uses composition (among other operators), currying, and partially applied functions, but it refers to something distinct from the sum of those concepts/features. Consider the following function of two parameters, written in Haskell/PureScript syntax: f x y = g x (m (h y)) = (g x . m . h) y We can now eta-reduce/partially apply f instead in the definition (which is possible because f is curried by default): f x = g x . m . h If I were using pointfree style at all (which I almost never do), this is where I'd stop. However, you can go the whole hog and strip out all the arguments: = g x . (m . h) = ((. (m . h)) . g) x f = (. (m . h)) . g I've made use of everything you listed, but it's only when you go this far that your function is truly free of "points".
- 9y ago
- harunurhan 9y agoI disagree, I don't know may be it's a preference thing but I think the code with points is readable for me and doesn't require to write extra function, class etc.
- avmich 9y agoA hallmark example of point-free style in J is likely the example of calculating the average value of the array: +/ % # Here literally you say "I'm calculating sum of elements (+/) which then divide (%) into the number of elements (#)". If your array isn't just a one variable (x) but instead is obtained as a result of an expression, you may not want to repeat that expression twice or to invent an intermediate variable to store the array result. Here point-free style can help.
- ibejoeb 9y agoOkay, I get the concept. Where is the argument for it? >Sometimes, especially in abstract situations involving higher-order functions, providing names for tangential arguments can cloud the mathematical concepts underlying what you’re doing. Sometimes? When? >point-free notation serves as an example of how subtle changes to the way you think about or define your code can have big impacts on readability. In the end, we're defining `atLeastTwo` and using it identically, so it would be much more compelling if there was an example in which things were, in fact, more readable. This example is just syntactic. It's like arguing that `const atLeastTwo = (x) => x >= 2` is better than `function atLeastTwo(x) { ... }` > It’s your job as a developer to be cognizant of these tradeoffs. It's just that this piece isn't really helping me get there. I think the author is suggesting that we define `atLeastTwo` in terms of an `atLeast` primitive, i.e., currying and partial application. It's clear that this is going to make composition easier, but it is relying on it. Now, I'd like to see how point-free, as a paradigm, yields better results in terms of readability or error prevention.
- fny 9y agoAside from this, there's a higher performance and maintenance cost. Performance since you've turned a single function call into two. Maintenance because now you need to test this `atLeast` primitive when odds are YAGNI.
- mattnewport 9y agoPerformance shouldn't be an issue if you have a decent optimizing compiler and even in JavaScript I would think a decent runtime should JIT away the overhead. Tangent but this is one reason C++ works nicely with a more functional style with lambdas - the compilers are pretty good about optimizing away any overhead resulting from function composition etc.
- fiatjaf 9y agoGo argue with Haskell benchmarks. Functional programming is slow.
- habitue 9y agoWe use point free notation in JavaScript most often with "fluent" apis like d3 or underscore, where instead of using an explicit compose operator like in haskell, each method in a chain returns the context with a ton of chainable methods defined on it. The big difference is that Haskell (and other ML-syntax languages) make currying the default so it's much less noisy syntactically to use pointfree style than it is in JavaScript. https://wiki.haskell.org/Pointfree https://wiki.haskell.org/Pointfree
- ape4 9y agoSo point-free avoids the parameters. Why not call it parameter-free. Or argument-free.
- jeremy_wiebe 9y agoAgreed. I thought I was going to read about message passing styles a la Objective C.
- lmm 9y agoThe concept originated in topology, where the parameters to your functions are points (in a space).
- danharaj 9y agoPoint-free isn't going to be worth it in a language where composition and application of functions isn't syntactically and computationally as cheap as possible. It also only works perfectly well for unary functions and situationally for binary functions. Anything more and the contortions start being more unreadable than variables. In Haskell partial application is literally free and composition is an operator. That's why point-free works well there. No point in importing syntactic ideas that don't make sense in the new context. Edit: I should also point out there's a subtle corner-case if you mix these transformations with uses of `seq`.
- falcolas 9y agoSo, you get a single point-free function, at the cost of two other point-full functions (at least one of which will utilize run-time introspection)? All you're doing is pushing the "point" somewhere else - in this case down onto Math.max. Seems like a net loss of simplicity and readability.
- savanaly 9y agoThat's only true if you never reuse Math.max. I believe the philosophy the OP is espousing is to write a few point-ful funcitons when necessary and then make the bulk of your business logic be recombinations of those in a point-free way, rather than redoing the points at every function the whole program wide. I will agree it is a big loss of readability for someone who's not used to reading programs that are written like that. And that includes me. But I have faith it will get easier to read and eventually I will find it easier to read code written in that style than code written in the old style.
- beaconstudios 9y agogiven the article neglects to provide an example, I have one handy from my git: https://gist.github.com/ajeffrey/092b71a3ad5f2034601574262c5c1167#file-cgol-js-L26 https://gist.github.com/ajeffrey/092b71a3ad5f2034601574262c5... Note how on this line, arguments are not present and passed around - the input of the function being declared is simply passed from function to function, with the output of each becoming the input of the next. When you can write code this way, it improves clarity by putting the focus on what the code is _doing_ rather than whatever arbitrary name you're giving the input parameters. It's definitely a tool for use in specific situations though as often the name of the arguments helps a great deal with readability.
- ArchReaper 9y agoThis seems like a great way to obfuscate all your code without any actual benefit. Can anyone give me a real example of how this is somehow a better way to do things? Maybe the application for this is only for a specific field that I'm not familiar with?
- mrkgnao 9y agoWorking with streams of data often makes pointfree syntax beneficial, since you can start to see the omitted arguments "flow" through your chain of composed functions. If you write f # g = \x -> g (f x) then you could implement functions like this: f :: Stream Int -> Stream Int f = drop 3 -- drop the first three elements # skipEvery 3 -- skip every third element # sumEvery 2 -- sum every consecutive pair of elements # map (* 2) -- double everything This would turn 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... into 2 * (4 + 5), 2 * (7 + 8), 2 * (10 + 11), ... You can find many non-pointfree versions of similar streaming code here: https://github.com/snoyberg/conduit/ https://github.com/snoyberg/conduit/
- chubot 9y agoFWIW I wrote a related blog post: "Shell Pipelines Support Vectorized, Point-Free, and Imperative Style" http://www.oilshell.org/blog/2017/01/15.html http://www.oilshell.org/blog/2017/01/15.html I'm also writing a blog post right now that uses this real instance of point-free style: count-nul() { od -A n -t x1 | grep -o '00' | wc -l } # usage: count-nul < myfile.bin The issue is that grep can't find NUL bytes, because argv is NUL-terminated. So you have to convert to hex, grep, and then count.
- mywittyname 9y agoComposition and currying are great if you're the only person working on a project, because you will understand everything that's going on. But my experience has been that developers will not inherently come to the same conclusions with this style, using this on a team of more than two leads to project fragility and incongruousness.
- ahayter 9y agoA couple of posts that really made this click for me (just beginning my programming career). This is javascript library, but the concepts do translate functional programming in general. http://fr.umio.us/why-ramda/ http://fr.umio.us/why-ramda/ http://randycoulman.com/blog/2016/05/24/thinking-in-ramda-getting-started/ http://randycoulman.com/blog/2016/05/24/thinking-in-ramda-ge... (Read all posts from the second link) After reading this series of blog posts and using Ramda.js for the last year or so, I can see the value here. But you do have to "get it" which means learning additional concepts, if the additional learning isn't a burden to your team (I hope your team isn't averse to learning new concepts) then I think it's an asset to be used when it makes sense.
- dfboyd 9y agoFrom a Clojure standpoint, I don't think there's much going on here beyond comparing this: (defn at-least-two [x] (max x 2)) with this: (def at-least-two (partial max 2))