11 ms·
Programming in the Point-Free Style
- dep_b 10y agoInteresting article. So far I've been working on one (real and deployed) project that employs F# and I generally liked the experience. Much of my code still is F#ified C# and lacks a cohesive way of structuring things, but every time a new piece of the puzzle snaps in place and I get better, shorter and more beautiful code to replace the old "works but ugly" stuff. The hardest thing is where reality (.Net frameworks, API's, user input and databases) meets theory (nice formatted mathematical situations that make F# shine).
- davnn 10y agoI have yet to see the benefits of point-free programming. Don't get me wrong it's fun, but in my opinion it makes reasoning about programs much harder. I am a huge fan of functional programming nevertheless. The opinion that a point-free style is "beautiful code" seems pretty esotheric to me.
- agentultra 10y agoFor a good, pragmatic introduction that may clear this up for you: https://www.youtube.com/watch?v=Cy7jBYr3Zvc https://www.youtube.com/watch?v=Cy7jBYr3Zvc tldw; point-free style can lead to readable, generic code -- but any dogmatic adherence to a particular style can have the opposite effect. Use it when it makes your code more readable, avoid it otherwise.
- davnn 10y ago> Use it when it makes your code more readable, avoid it otherwise. That's probably the most useful approach to point-free programming.
- kungtotte 10y agoIt's the most useful approach to anything when it comes to programming. Rigid adherence to rules, styles, paradigms or whatever will generally result in at least some corner cases where adherence leads to worse code.
- Flow 10y agoThe Haskell style guide says not to over-use them, and to write a type-signature on the line before. https://github.com/tibbe/haskell-style-guide/blob/master/haskell-style.md https://github.com/tibbe/haskell-style-guide/blob/master/has...
- acchow 10y agoAvoid over-using point-free style. For example, this is hard to read: -- Bad: f = (g .) . h heh
- AnimalMuppet 10y ago"For those who find beauty in this kind of thing, this is the kind of thing they find beautiful." (Twisted paraphrase of a somewhat famous quote. See http://quoteinvestigator.com/2015/09/09/like-sort/ http://quoteinvestigator.com/2015/09/09/like-sort/ for more.)
- jfoutz 10y agoFor me, point free seemed very tight. I didn't struggle to much with reasoning about what was happening, but changing things. The only way i could really cope was lots of classes. leave the current instance in place while i write a whole new instance with the new approach. Attempting to change an existing instance, like i'd do with a more verbose language, seemed very difficult if not impossible for me.
- deleted 10y ago[deleted]
- JoshTriplett 10y agoHow do you feel about shell pipelines? Most shell pipelines don't name the things they operate on. Point-free style feels the same way: you string functions together and don't name the input/output.
- davnn 10y agoI like them. It's not that I don't like point-free functions but more often than not specifying the point makes sense in my opinion — more clarity, less beauty.
- JoshTriplett 10y agoI agree. My general rule is that any non-trivial use of "flip" or any use of curried '.' or '$' means I should name the point instead.
- eru 10y agoDepends. I like using (.) for eg chaining functors, even though it's not trivial: (fmap . fmap . fmap) toUpper is a function that goes three levels down and applies f, eg on a 'Maybe (Int, [Char]))' and I find this style easier to read than: fmap (\i_string -> fmap (\string -> fmap (\c -> toUpper c))) But chaining 'fmap's in this way might be a special idiom that people can get used to, and not say anything about using point-free style in novel situations. The nice thing is that you can mix 'fmap' and eg 'traverse' like this.
- JoshTriplett 10y agoI think in that case, for clarity, I'd rather write: f (Just (i, cs)) = Just (i, map toUpper cs) f x = x Especially because I find the instances that apply fmap to the second item of a two-item tuple confusing. But in general I don't mind that kind of use of '.'; I was talking more about any expression like "g . (f .)" or similarly inscrutable curries.
- 10y ago
- jlg23 10y ago> [tacit programming] makes reasoning about programs much harder With sufficient type information reasoning is easy enough that even the compiler can verify correctness. > The opinion that a point-free style is "beautiful code" seems pretty esotheric to me. In some way one could say that tacit programming is the interface-oriented programming of the FP-world. I just had an example where I used tacit programming and had much cleaner code: I had to talk to PostgreSQL through "some" API. For that I need to connect, authenticate and potentially configure the connection. The toplevel function looks like this (in CL): (defun connect (open authenticate configure) ;;; omitting the (check-type ..)s on the functions here for brevity (funcall (compose configure authenticate open))) There is of course a wrapper that creates the 3 functions for you for the regular use cases, but if you want to implement a connection through IP-over-carrier-pigeon or authentication based on DNA samples, you can do so without having to touch the library - just pass your own open/authenticate/configure functions. And the benefits are just the same you have in OOP when using interfaces properly: Decoupling of logic and implementation.
- davnn 10y ago> With sufficient type information reasoning is easy enough that even the compiler can verify correctness. I think we have a different opinion on "easy to reason about". With easy to reason about I mean the time it takes me to understand a function or part of a program. I can write the most cryptic function with cryptic types that is 100% correct but nobody understands. > And the benefits are just the same you have in OOP when using interfaces properly: Decoupling of logic and implementation. No, there are no real benefits as it's only a syntactic difference. Generally, a point-free style should only be "easier to understand" when the point you decide not to mention is completely irrelevant and non-meaningful to understanding the function, but that's not always the case in my opinion.
- jlg23 10y ago> I think we have a different opinion on "easy to reason about". > No, there are no real benefits as it's only a syntactic difference. Yes, but an implementation in brainfuck can be derived by pure syntax transformation as well and thus too is "only a syntactic difference". I personally find it much easier to reason about 6 consecutive lines of code that enforce 4 interfaces by type annotations (the 2 lines quoted plus the 4 lines of type declarations I omitted) than to read through the equivalent in java, which is at least 12 lines in 4 files and requires an abstraction very far away from underlying mathematical principles (the equivalent of my code in Java would require factories of factories and I'd argue that if one considers that easier to read, one has spent too much time in OOP-land). As others have pointed out, one should definitely not get carried away with it and always evaluate carefully whether it helps clarity. But when the protocol is as simple as "connect, authenticate, configure" then I don't see any reason why I should not write it exactly like this: (compose configure authenticate connect)
- rbehrends 10y agoSome typical pragmatic applications: 1. Escaping parenthesis hell: sometimes you can escape an unreasonable amount of parenthesis nesting by using (some) point-free programming. 2. The same thing that you'd use cascades in Smalltalk for: repeated applications of operations to the same object without having to name the object over and over again. (Obviously, in a functional language, it may not strictly be the "same" object, but conceptually, it's the same pattern.) 3. Combinator libraries (mentioned by the article itself). Here, the functions being composed are really meant to be (mostly) opaque entities and the fact that you're using a form of function composition is an implementation detail.
- coldtea 10y ago>1. Escaping parenthesis hell: sometimes you can escape an unreasonable amount of parenthesis nesting by using (some) point-free programming. Why would I want to? Parenthesis make precedence explicit.
- eru 10y agoParens can be harder to parse for humans than operators. That's the whole point that justifies not going for S-Expression syntax for every programming language in the first place.
- michaelmrose 10y agoIndentation can help but personally I like my editor to color matching parens the same color making it easier to visualize structure.
- eru 10y agoOh, I definitely have a soft-spot for s-expression syntax---especially with good editor support for colours and indentation and syntax-aware editing. It actually took my quite a while to warm-up to Haskell's richer and more complicated syntax.
- emodendroket 10y agoTurn around and run any time someone starts talking about "beautiful" code.
- recursive 10y agoI'm not so sure. Taking care that you're only exposed to ugly code won't really increase the chance that it's any good.
- emodendroket 10y agoPeople who obsess over code "beauty" tend to write unreadable and unmaintainable code and spend a long time doing it.
- wolfgangK 10y agoPoint-free programming would be extremely useful if the composition primitive allowed introspection. You would then compose functions that would have run-time homoiconicity as you would be able to edit the "ast" embedded in the point-free function. I use this to modify the parameters of the composite geometric transformations of fractals. This allows me to generate animations for any kind of fractals without having to reimplement the animation code for each fractal : https://scientific-coder.github.io/Playground/2017-03-20-fractals.html https://scientific-coder.github.io/Playground/2017-03-20-fra... (cf stepify multi-method). This would be useful for web servers in Clojure as we could have our cake and eat it too : while currently one has to pick either easy with ring handlers that are simple function compositions and vectors of interceptors for pedestal that can be modified at runtime.
- dustingetz 10y agoIt's beautiful because it maximizes use of existing ideas and minimizes the introduction of new ideas. Idea :: code dependency. It is worth minimizing dependency complexity and it's too bad that today's software and languages is still so hard to think in that factors like this are largely overshadowed.
- sullyj3 10y ago> The opinion that a point-free style is "beautiful code" seems pretty esotheric to me. It feels to me like there's this weird disconnect between people who talk about beautiful code and those who don't. For me, the aesthetic criteria that would lead me to call a piece of code beautiful are things like clarity, ease of understanding and simplicity. Writing code in a certain way might even elucidate the structure of the problem for the reader. Now, concision is definitely something that matters to me aesthetically, but if it comes at the cost of clarity, it just isn't worth it. I would consider very concise but complex and inscrutable point free code to be ugly. It definitely is my opinion that much of the time point free code is beautiful by these criteria. It's also often the case that I find point free code ugly. The thing that makes it difficult is that whether code is readable is heavily subjective. It very much depends on the familiarity of the reader with the functions and combinators and constructs that are in use. And of course people differ on the level of fluency in the language and language constructs that should be expected of the reader.
- lmm 10y agoWhen you're writing a simple, generic function, naming your arguments can easily feels like pointless (heh) ceremony that just obscures rather than clarifying. def foo(whatever: Int) = quxxl(bar(whatever)) val foo = bar andThen quxxl Naming "whatever" just gets in the way of making it clear what foo actually does.
- pcmonk 10y agoPoint-free programming is sometimes also called "tacit" programming. I found this practical description of tacit programming in Racket to be useful: http://r-wos.org/blog/tacit-racket http://r-wos.org/blog/tacit-racket
- mxfh 10y agoTwo points particularly add to the complexity of the point-free definition
- rnhmjoj 10y agoFor haskell there is a fantastic tool that converts code into a pointfree function definition. https://hackage.haskell.org/package/pointfree https://hackage.haskell.org/package/pointfree
- JadeNB 10y ago> For haskell there is a fantastic tool that converts code into a pointfree function definition. But why? If a point-free style is more readable, then by all means use it, but if you need an automated tool to do the translation for you, then that seems unlikely. If it's an efficiency question, then your compiler can, and (given the trickiness of GHC in particular) probably will, do anything that an external automated tool can.
- rnhmjoj 10y agoFor fun mostly but it can be useful too: it can find neat solutions I couldn't think of and I may decide to use it. Of course it gives also crazy stuff like `(((.) . (,)) .)` that I will never put in a program because I don't understand or it's just worse than the original input.
- JadeNB 10y ago> For fun mostly but it can be useful too: it can find neat solutions I couldn't think of and I may decide to use it. Of course it gives also crazy stuff like `(((.) . (,)) .)` that I will never put in a program because I don't understand or it's just worse than the original input. If it is purely an intellectual exercise, to see how else the code could theoretically be written, then I totally see the point. (I am a mathematician, after all!) On the other hand, if it is code that you actually use in production, then surely the old dictum that I will misquote, for want of looking it up, as "if you write the cleverest code possible, then you are not clever enough to debug it", applies all the more so to say "if you aren't clever enough to write your own code, then you certainly aren't clever enough to debug it"! (Not you personally, I mean; just a comment on programmer-facing auto-generated code.)
- scandox 10y ago> A category consists of abstract functions (or morphisms, or “arrows”) that have an origin and destination (or domain and codomain, or “argument type” and “return type”). I think perhaps Maths people need to seek more consensus on terminology.
- shmageggy 10y agoSeriously. I've tried to read on category theory before and never really grokked what it was all about. The sentence you quoted was literally the first one I've ever read that actually made me say, "oh, that's what this is".
- houli 10y agoThe category theory idea of a morphism is more abstract than a function. If functions deal with domains and codomains then certainly morphisms need new words to deal with the abstractions of those ideas that are specific to functions.
- mikebenfield 10y agoI've only seen mathematicians use the terms morphisms, arrows, and domain/codomain. It is true that morphisms and arrows mean the same thing, but I think that's because the term morphism is specific to category theory and the term arrow is from graph theory. All the other terms here (abstract functions, origin, destination, argument type, return type) appear to be the author's attempt to make the subject understandable to programmers.
- johnloeber 10y agoThe term "arrow" is from category theory, not graph theory.
- fabianhjr 10y agoMorphism is from Algebra/General Mathematics and it is a function that preserves a "structure". (Group Morphism, Isomorphism between sets, etc) In Graph Theory the terminology is Node and Vertex. Arrow, Source and Target are from Category Theory.
- astrobe_ 10y agoPoint-free is generally the main style for concatenative languages. In order to be able to read it, you have to know by heart the signature of the standard operators, and then you have to learn on the go the signature of the operators of the particular program your are studying. It doesn't fly well if the author doesn't follow certain "usability" rules: - name things thoughtfully and properly, - no more than 3 arguments for your functions, - keep the complexity of the functions as low as possible. This is good advice in any language, but I'd say that with concatenative languages at least, deviations are likely to have more acute negative consequences on maintainability.
- simplify 10y agoI think concatenative languages have great potential when paired with a proper type system and IDE. But you're absolutely right when it comes to plain text programming; point-free style feels clever to write, but makes it more difficult to read.
- eru 10y agoIn Haskell there are some rules of thumb: if your new function is just a composition of existing function, point-free is fine. If you need 'flip' and friends, consider giving points a chance. There's a grey area in the middle that asks for judgement calls.
- Silhouette 10y agoif your new function is just a composition of existing function, point-free is fine. I'd probably extend that to things like straightforward partial application as well, but I strongly agree with your implied point that once some new function isn't a relatively simple derivation from some existing function(s), point-free style tends to become a liability. It's cute doing things with weird combinations of operators like (.) and ($), but it's also the Haskell version of writing C code full of one-character identifiers and obscure macros.
- kazinator 10y ago
- JadeNB 10y agoLots of the comments seem to be missing the point that, despite the title, the author is not advocating the point-free style: > The point of this exercise is to illustrate how the lambda calculus with pattern matching (or set theory for that matter) is superior to the point-free style in terms of expressive efficiency.
- Avshalom 10y agoSure but it's a deeply uninteresting exercise because no one programs in category theory.
- AnimalMuppet 10y agoNo one programs in category theory, therefore... what? ELI5: What's the connection between your comment and JadeNB's?
- Avshalom 10y agoPeople aren't missing the point, they just aren't engaging with it because TFA makes it's point by claiming category theory is expressively inefficient... but nobody programs in category theory, the link between point free programming in F# and category theory is meaningless. Tacit programming in F# is a reflection on tacit programming in F# nothing else. So the commentariate has decided to talk about point free programming in general instead of engaging the article's very narrow and unsubstantiated point.
- mpweiher 10y agoHaving done a lot of programming in Postscript, I understand the allure of point-free style. Some code can seem extremely elegant, but mostly it's just clever, and an exercise in showing off how smart you are and how much you can keep in your head. And with all due respect to Joe Condon: Eschew clever code.
- finnh 10y agoInteresting. I find composing operators on streams to require holding less in my head than the equivalent non-point-free (ie, iterative / loop-based) code. When reading (eg) nested for loops with conditions, I have to push a ton of context variables into my head, and then evolve them over iterations if I'm doing a close read. That's a lot of ask of my head. But in the point-free style, each compositional unit tends to be small enough to be "obviously no bugs" vs. "no obvious bugs".
- comex 10y agoAll things in moderation. Chaining list transforms (like map and filter) in one expression does have fewer points than giving every intermediate result a name or even using a for loop, but it doesn't require the level of tenseness usually associated with the term "point-free".
- mpweiher 10y agoI think you're mixing up "higher order" code and "point-free" code. Just because you don't go point-free doesn't mean you have to resort to nested for-loops.
- sp332 10y agoPoints-free is helpful if you're mentally focusing on the "verb" or function, and not the "noun" or data. (sqrt x) yields a noun. (sqrt) yields a verb.
- drudru11 10y agoWas the language Forth point-free?
- Jtsummers 10y agoYes. That was my introduction to the concept (though I didn't know the term until I later toyed with J and Haskell).
- abecedarius 10y agoAnd another example is FP from Backus's Turing Award lecture.
- RodgerTheGreat 10y agoIt can be. Pure functions which exclusively operate upon the stacks are often considered idiomatic. If you choose, however, you can reference and alter named global variables within a function, and some dialects offer a facility for named local variables. You might argue that the 'i' and 'j' words, which interact with loop constructs to give you a copy of the current and outer loop induction variable, count as references to named variables, even if they aren't strictly the same thing. As with most languages it comes down to the programmer.
- mh-cx 10y agoAfter a few seconds I was redirected to an ad site that made my mobile vibrate and that “locked me in“ by some redirect trickery which blocked the back button. Not really what I'd expect from a HN link with so many upvotes. Did no one else experience this?
- kazinator 10y agoPoint free expression in production ANSI C90 code: Function defined here, stored in xform_listed_quote_f global: http://www.kylheku.com/cgit/txr/tree/eval.c?id=6ca6be767f8ac84a8c601c08d6e06822ced556f9#n3043 http://www.kylheku.com/cgit/txr/tree/eval.c?id=6ca6be767f8ac... Used as a mapping function here: http://www.kylheku.com/cgit/txr/tree/eval.c?id=6ca6be767f8ac84a8c601c08d6e06822ced556f9#n3108 http://www.kylheku.com/cgit/txr/tree/eval.c?id=6ca6be767f8ac...
- sddfd 10y agoGreat article. 100% agree. Especially the category theory example.
- dcre 10y agoA great talk from Strange Loop 2016 on this topic: "Point-Free or Die: Tacit Programming in Haskell and Beyond" by Amar Shah https://www.youtube.com/watch?v=seVSlKazsNk https://www.youtube.com/watch?v=seVSlKazsNk
- z3t4 10y agotry not to make global functions. or at least put them in name spaces. if the function is reusable cross code bases then make it into a module or library.
- tempodox 10y agoI think the article makes good points. We have already seen callback hell. I wonder how long it will take before we coin the term “monad hell”.