5 ms·
Point-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 ope
by astrobe_ 10y ago
Point-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 agoIn order to be able to read it, you have to know by heart the signature of the standard operators Unfortunately, while that tells you how many operands there are, it doesn't help you find them in the surrounding syntax. In Forth, for instance, the operands of a + could be a pair of immediately preceding constants like 1 2 +. These could be far away in a contrived way: 1 2 [... snip stuff that produces side effects and keeps stack balanced ...] +. Or just due to expression complexity: [30 word expr] [27 word expr] +: the left operand of + is the result of the 30 word expr, which is 27 words away from the + symbol. Or is that 26? 29?
- nine_k 10y agoThe 30-word expr should have been another word, or two.
- mrkgnao 10y agoSo German trennbare Verben (split verbs)? :) Whenever the literary German dives into a sentence, that is the last you are going to see of him till he emerges on the other side of his Atlantic with his verb in his mouth. - Mark Twain
- astrobe_ 10y ago> These could be far away in a contrived way: 1 2 [... snip stuff that produces side effects and keeps stack balanced ...] + This is obviously poorly written. "1 2 + [stuff]" is the sane way to write it. Or "2 [stuff] 1 +" if [stuff] takes one parameter and outputs one result. > Or just due to expression complexity: [30 word expr] [27 word expr] + Post that on comp.lang.forth, and people will certainly tell you "don't do that" or "why do you that" if it's less trivial than a factoring issue. In similar situations some people have this trick of separating sub-expressions with double spaces. But that's ignoring the writing on the wall. What I learned from Forth is that it's true that it has its shortcomings, but I have my own shortcomings too. As Forth is a DIY language, you have to make this distinction in order to improve language/programmer pair.
- kazinator 10y agoThis is obviously poorly written. It's only obvious in the example form which I have. If such a thing occurs in real code, the stuff won't look like [stuff]; it will not stand out. "Poorly written" happens.
- JoshTriplett 10y ago> - keep the complexity of the functions as low as possible. This seems particularly critical. This style works well in Haskell where it's extremely common to have short functions. I've found that Haskell code often has two kinds of functions: 1-2 line functions (or function cases) that do actual work and computation (often combining other such functions), and 50+ line monadic functions that implement simple sequential routines with very simple control flow (calling other functions for any non-trivial computation). Point-free style works quite well for many of the former.
- krakensden 10y agoI don't do Haskell, but I have worked on projects with lots of small functions, and one of the problems with that is that can be... too many functions. You have to go look up three functions every time you read a block of code. Worse, people start writing the same functions over and over again. There's probably a bigger picture problem and fix in that kind of situation, but it is something I think about.
- JoshTriplett 10y ago> You have to go look up three functions every time you read a block of code. When you write small, single-purpose functions, you can make them fairly self-documenting with the name alone. If you have to look up a one-line function, it probably doesn't have a very clear function or name. > Worse, people start writing the same functions over and over again. That one I've definitely run into. > There's probably a bigger picture problem and fix in that kind of situation, but it is something I think about. Mechanisms to search functions by type ("find me a function that takes an X and returns a Y") help quite a bit.
- mrkgnao 10y agoConventions also help. For instance, the lazy readTVar, modifyTVar, etc. functions have strict analogs called readTVar' and so on. mapM, foldM, replicateM, and traverse return a monadic value, while mapM_, foldM_, replicateM_, traverse_ and friends discard the return value (giving an m () instead of an m a).
- jblow 10y agono more than 3 arguments for your functions It is hard for me to believe that this is a piece of style advice that anyone writing serious software would follow. If you need to do something wherein the basic task requires 8 arguments' worth of information (which happens A LOT) then trying to factor that into 3-argument pieces is going to give you something Byzantine that is probably also buggy (and it could get extremely heinous, in a way determined by the data dependencies internal to the procedure you are factoring). And if you somehow succeed at all this, congratulations, you just did a bunch of engineering that did not improve the functionality of your software in any way. (In fact it probably made the software take longer to compile). If doing a certain job needs 8 pieces of information, it needs 8 pieces of information. It doesn't help anyone to try to break that up. Similarly with this: keep the complexity of the functions as low as possible Not really. If you are just factoring some block of complexity in to 4 blocks of less-complexity, well, now you have the same amount of complexity as the original code, plus the complexity of the call graph, and the fact that the person who comes along to read the code will not be able to clearly see the control flow. There definitely are many cases when factoring a procedure into simpler things is beneficial. But to claim that it's a good idea all the time, or even half the time, is I think mistaken.
- fazzone 10y agoA guideline like "three arguments to a function" says nothing about the type of those three arguments. You are right that there are many jobs that require 8 or more pieces of information, but often those parameters can be grouped into structures which each hold multiple pieces of information. The point is not to create types for every function just to hold their arguments, but that well-thought-out data abstraction can simplify things by factoring data into named structures that are used in multiple functions. For example you could have a drawRectangle function which takes four parameters (x1, y1, x2, y2) of some primitive numeric type, or you could create a Point type and use just two of those. This improves clarity because it is obvious just from the signature of drawRectangle(Point a, Point b) that you are specifying the endpoints of the rectangle, whereas four numeric arguments could mean (x1, y1, x2, y2) or (x, y, width, height).
- 10y ago