3 ms·
I love the concept of point-free programming - write your function by simply concatenating the transformations you want. I just hate reading the resulting code
by TuringTest 3y ago
I love the concept of point-free programming - write your function by simply concatenating the transformations you want.
I just hate reading the resulting code written by others. What information is expected to come in, and exactly what data passes from one step to the next, and in what position? Data type signatures only go so far.
Point-free means you have all that wiring in your head, without assistance from the notation.
- anonzzzies 3y agoI like reading and writing point-free code, I just really hate debugging it. The debuggers/IDEs are (usually? are there exceptions) not really geared up for it and so the debugging experience is basically one of just having 1 call. This goes for more lambda style calling mechanisms of course. I end up pulling it apart, but that's only because of bad debug/ide support in my case. I can read/write it fine; in most cases it just works and then I like it better than the (verbose) alternatives.
- euiq 3y agoRecent versions of F# can stop on individual function applications in an expression like x |> f a |> g b … (search for "pipeline debugging" on < https://devblogs.microsoft.com/dotnet/whats-new-in-fsharp-6/ https://devblogs.microsoft.com/dotnet/whats-new-in-fsharp-6/>). In my experience, these are more common than strict point-free style anyway.
- astrobe_ 3y agoJust like there is "decision fatigue" [1] I believe there is "naming fatigue", and naming are one of the three great problems of computing, as everyone knows. I'd say that at least point-free prevents naming fatigue, but for the result to be nice to the reader, the naming and factorization is much more important than with explicit code, which has sort of much more "safeties". On the specific question you ask, let's hear the creator of one of the major stack oriented languages, Forth, by its inventor when he had about 30 years of professional practice. He talks about code comments, which is the local solution that comes immediately to mind when thinking about the issues you pointed out: So people who draw stack diagrams or pictures of things on the stack should immediately realize that they are doing something wrong. Even the little parameter pictures that are so popular. You know if you are defining a word and then you put in a comment showing what the stack effects are and it indicates F and x and y F ( x - y ) I used to appreciate this back in the days when I let my stacks get too complicated, but no more. We don't need this kind of information. It should be obvious from the source code or be documented somewhere else. Well, comment-less code isn't popular either, but we're not here to design the next Python anyway. By the way, Chuck Moore evolved his language until he determined that the remaining problems were "in the hardware". He did his own CAD tools with his language to design stack-oriented CPUs with some success, as some of them ended up in spacecrafts (notably Philae in 2014 [3]). In my experience, when you want to keep the data flow as simple as possible (in Forth: keep "stack juggling" to a minimum), there's very often only one good order for parameters. That works very well for the writer, for whom the understanding of the problem helps with memorizing signatures and semantics. As a reader (of other's code), I have much less experience, but I guess that understanding the same things requires the readers to invest more time up front, in addition to the "accidental obfuscations" (poorly written code, unusual programming habits and conventions...). [1] https://en.wikipedia.org/wiki/Decision_fatigue https://en.wikipedia.org/wiki/Decision_fatigue [2] https://www.ultratechnology.com/1xforth.htm https://www.ultratechnology.com/1xforth.htm [3] https://en.wikipedia.org/wiki/RTX2010 https://en.wikipedia.org/wiki/RTX2010
- valty 3y ago> "naming fatigue" ChatGPT is quite good at coming up with names. It can map a description of a thing to a name. And the descriptions can vary between developers and probably still end up with the same name. So if everyone relies on its naming-prowess then everyone ends up naming things consistently. Aside: I wonder if it can name parameters from tacit-style... Write this in non-tacit style: sort input.txt | uniq -c | sort -rn > output.txt sort input.txt > sorted.txt uniq -c sorted.txt > counted.txt sort -rn counted.txt > output.txt Seems it can. Look how the output of `uniq` was `counted`. Maybe we should just let ChatGPT name everything. Interesting to think what programming is you no longer have to name things. A lot of programming is grouping code and coming up with names for the groups. What if we didn't worry about grouping or naming, and just dealt with the data, and let chatgpt provide names on-the-fly. Functions could even be replaced by descriptions of the actions. I wonder if you could let an LLM evolve a graphical operating system from just the hardware definition. We went through ~70 years of trial and error for programming languages going from low-level to progressively higher-level, and kept all the baggage along the way. How would an AI go about this...with all the knowledge of today? How would it reason about each step in evolving a language. I'm guessing it would probably be a LISP :D
- sherburt3 3y agoI think this style requires familiarity with the codebase that its using before it actually becomes readable, but once you have that familiarity reading and understanding what's happening is a lot faster. Like Rxjs for example, if you're unfamiliar with the library its like hieroglyphs, but once you develop familiarity with it you can read other people's code much faster than if everything was implemented procedurally. That said if people are paying me money I usually avoid doing point-free, except for Rxjs. I love Rxjs so much.
- tonyarkles 3y agoI'm smiling a bit because I know exactly what you mean and generally agree, with an exception. A common idiom in Elixir is to return {:ok, result} or {:err, :reason} from calls that can fail. Leaning on that idiom, good function names, and good errors goes a long way: params[:id] |> find_user |> verify_user_has_access_to(params[:post_id]) |> etc When I define the functions I'll use pattern matching on the first argument to each function so that you can either pass in a "user" or {:ok, user} or {:err, _}. If it matches the error pattern it'll just return the error unmodified. The errors have enough fidelity to make it clear where the pipeline failed. I'm not sure this is a super common pattern though but it worked pretty well for me.
- sdeframond 3y agoI call it "railway oriented programming", after the presentation by Scott Wlaschin. https://fsharpforfunandprofit.com/rop/ https://fsharpforfunandprofit.com/rop/
- nephanth 3y agoI am not sure about Elexir, but in Ocaml, someone using this style would gravitate towards Result.map and Result.bind (Defined like this: given (Ok a) or (Err b), - (Result.map f) returns (Ok (f(a))) or (Err b) - (Result.bind f) returns f(a) or (Err b) ) One could then write params.id |> find_user |> Result.bind (verify_user_has_access_to params.post_id) |> etc Reasons for that are 2-fold 1. It de-clutters your functions (you do not need that match statement anymore) 2. It becomes evident - which functions will simply pass down errors (bind or map) vs. which ones may handle them - which functions may raise new errors (bind can, map can't)
- trealira 3y ago> Point-free means you have all that wiring in your head, without assistance from the notation. I completely agree. It fatigues me to read unnecessarily point-free programming. I have to translate it into a point-ful style in my head to understand it. For example, you could take this piece of Haskell code and make it more point-free. I think it's readable at first, if redundant (you could remove the last parameter xs, for example). -- map: apply the function f to each element of the list xs map :: (a -> b) -> [a] -> [b] map f xs = foldr (\x xs -> f x : xs) [] xs Some people would prefer to write it like this: map :: (a -> b) -> [a] -> [b] map f = foldr ((:) . f) [] That lambda takes more effort to parse and think about, though, at least for me. The first version was pretty readable. Now, when I read "((:) . f)" I'm thinking "okay, so the function f takes an argument, and passes the result to the (:) function, which normally takes two parameters; with one argument, it returns another function that takes one list parameter and returns it with the result of "f x" prepended to it." And to do this, I have to know implicitly how many arguments the function (:) takes in order to parse and understand it correctly (though in this case, it's obvious, because (:) is ubiquitous). Pointfree.io would take what I wrote and transform it into map = flip foldr ([]) . ((:) .) But I'm pretty sure no one would write that. It takes even more effort to correctly parse this. That said, I don't write any Haskell. I just used to try to, but found I didn't like it.