6 ms·
Absolutely every single time when I use functional programming, I store my intermediary calls in a variable, specifically because naming that variable forces me
by mihaic 3y ago
Absolutely every single time when I use functional programming, I store my intermediary calls in a variable, specifically because naming that variable forces me to explain what that intermediary result should be.
If the intermediary result makes no sense, and only the function composition makes sense, I'll create a new well named function that does the chaining, even if it's single use.
This is literally the only way I've seen multi-year projects be maintainable, and anything else eventually breaks down into uninteligible code.
Writing code for other developers in the long-run is empathy for yourself, since you'll eventually forget all the context you once had.
- mirekrusin 3y agoTacit programming shines when you have combinators over some closed, well defined domain - ie. parser combinators, runtime assertion combinators, iterator combinators etc.
- auggierose 3y agoYeah, point-free sounds cool, until you actually try it out. Even in their example they are not point-free: compose(foo, bar, baz) Here compose is applied to three "points" (which happen to be functions).
- epgui 3y agoThat example is as point free as it gets. Just because the syntax doesn’t look like Haskell doesn’t really change that.
- auggierose 3y agoYou are applying functions to arguments, aren't you? So "point-free" means you cannot apply a function to arguments on the left hand side of a definition, but you are allowed to do so on the right hand side? If that's point-free, it is also point-less.
- pasquinelli 3y agotacit means a definition doesn't name it's arguments, not that it isn't applied to arguments. the word tacit means implied, so arguments are implicity worked upon, instead of explicitly. tacit doesn't mean functions are nullary.
- auggierose 3y agoYes, but that is the same as not applying a function to arguments on the left hand side of an equation, but still doing so on the right hand side. The question is, what is the point of not doing that on the left hand side, if you then go and still do it on the right hand side? Let me say it again: It's pointless.
- epgui 3y agoIf you tried to understand as hard as you're trying to argue, you wouldn't be having this argument.
- auggierose 3y agoThere is nothing to understand here. As I said, it's pointless. From the Wikipedia article: > Tacit programming is of theoretical interest, because the strict use of composition results in programs that are well adapted for equational reasoning. Now this is bullshit. See my explanation, as equations are symmetric, and you can swap left and right hand side. Maybe you should try to understand harder.
- epgui 3y agoNo, you’re just failing to understand what “equational reasoning” means. It has little to do with the idea of LeftSide = RightSide. In fact you don’t even need an “equation” in that sense, or any equality sign, to do equational reasoning. Equational reasoning is when a program is evaluated (ie.: like when you simplify or factorize an equation in maths) by using substitution (or rewriting) rules: https://en.wikipedia.org/wiki/Rewriting https://en.wikipedia.org/wiki/Rewriting
- deleted 3y ago[deleted]
- lkuty 3y agoIMO It is point free even if the syntax is not as nice as Haskell. Same as foo . bar . baz
- deleted 3y ago[deleted]
- tome 3y agoPoint-free means that you don't bind variables, so this particular example is indeed point-free.
- gorenb 3y agoI use point-free languages, and it’s completely okay if you don’t enjoy them. It’s just that I love them, and they’re fun for me, so I don’t like when people are just saying that they’re like objectively bad. Try them out, see if you like them! I personally like a concatenative/stack-based flavor, but you might not! :)
- kjqgqkejbfefn 3y agoI've been using tacit programming intensively to the extent I get rid of most variables. I use both syntactic threading macros and functional combinators to achieve this. It is a double-edged sword in that it can make code as ugly as the original code it is trying to improve upon, but working without variables isn't that difficult nor does it lead me to intense clusterfucks. Composing lambdas contribute to this a lot more though. As for making things clearer for everyone, well, this is code I work on solo (hobby), but I think providing example inputs as well as debugging macros can help a lot. Consider the following: (defn parse-it [dependencies] (pp->> dependencies str str/split-lines (keep (|| re-matches #"- (\d+(?:\.\d+)?) -> \[((?:\d+(?:\.\d+)?(?:, ?)?)+)\]")) (>>- (map-> (juxt-> second (->> third (re-seq #"(?:\d+(?:\.\d+)?)"))))))) (parse-it (str "- 4 -> [1, 2, 3]\n" "- 5 -> [4, 2]\n" "- 6 -> [1, 2, 5]")) ;; The use of pp->> will lead to this getting printed ;; ->> dependencies : "- 4 -> [1, 2, 3] ;; - 5 -> [4, 2] ;; - 6 -> [1, 2, 5]" ;; str/split-lines : ["- 4 -> [1, 2, 3]" ;; "- 5 -> [4, 2]" ;; "- 6 -> [1, 2, 5]"] ;; (keep (|| re-matches #"- (\d+(?:\.\d+)... : (["- 4 -> [1, 2, 3]" "4" "1, 2, 3"] ;; ["- 5 -> [4, 2]" "5" "4, 2"] ;; ["- 6 -> [1, 2, 5]" "6" "1, 2, 5"]) ;; (>>- (map-> (juxt-> second (->> third ... : (("4" ("1" "2" "3")) ;; ("5" ("4" "2")) ;; ("6" ("1" "2" "5"))) In the end I think it doesn't really bring a lot, but it's especially useful in making short piece of code more readable: (->> [1 2 3 4] (map (when| odd? inc))) ;; vs (->> [1 2 3 4] (map (fn [x] (if (odd? x) (inc x) x)))) ;; result (2 2 4 4) Or this: (-> k-or-ks (when-not-> coll? list) (map-> ...do-something)) ;;vs (let [ks (if (coll? k-or-ks) k-or-ks (list k-or-ks))] (map ...do-something ks)) Or even this: (-> 1 (juxtm-> :incd inc :decd dec)) ;; vs (let [n 1] {:incd (inc n) :decd (dec n)}) Granted I have insane shits like this teleport arrow (very useful though): (-> '(1 2) (•- (conj (-• first dec)))) ;; => (0 1 2) Or this >-args "fletching" (-> {:a 1 :b 2} (•- (-> (>-args (-> (/ (-> :a) (-> :b)))) (->> (assoc (-•) :result))))) ;; => {:a 1, :b 2, :result 1/2} I actually write this kind of stuff in my code ahahaha (the right move is to implement assoc-> of course hahahaha). Now there are combinators I wrote I never use, like departializers, unappliers, argument shifters, etc
- jonahx 3y agoDoing this for every intermediate result badly harms readability. Consider McIlroy's famous 1-liner: tr -cs A-Za-z '\n' | tr A-Z a-z | sort | uniq -c | sort -rn | sed ${1}q Adding intermediate variables like "newline_delimited", "lowercased", "sorted", etc... is just pure noise. It's the equivalent of a newb programmer putting a comment over each line of simple code explaining in English what that code does, despite it being clear already. "There are only two hard things in Computer Science: cache invalidation and naming things" One of the great benefits of pipelines and tacit programming is the ability not to name intermediate results, especially when those results speak for themselves, are not re-used, and have no significance in isolation.
- mihaic 3y agoWhile I agree that sometimes I do break my rules myself, when I don't want to spend 15 minutes to name something, I don't think your example convinced me of your case. Would you honestly expect someone to know by heart what "sort -rn" or "uniq -c" do? This forces the reader to know what all the arguments mean by anyone reading this code. If you'd try to push this code, I wouldn't let it pass code review without a comment on each line (except plain "sort", that one's really obvious).
- jonahx 3y ago> Would you honestly expect someone to know by heart what "sort -rn" or "uniq -c" do? For people that program regularly in bash, I would. If it was a rare bash script in a code base where many team members didn't know bash well, comments would be appropriate. Even there, though, that's not the same as introducing superfluous intermediate variables. The larger point here relates to "intended audience" or "what competencies may I assume the reader has?". This is a matter of art, not science, and highly context dependent. Take an extreme version of the point you just made: const x 4*2; "Would you honestly expect someone to know by heart that `*` in JS means multiplication?" Clearly that is absurd, because that knowledge is an assumed competency. What about `**` for power? What about `^` for XOR? What about knowing that `-~x` is equivalent to `x+1`? Where exactly do you draw the line? You can "err on the side of over-commenting" but only so much, because taken to an extreme it hurts readability, and will annoy everyone. Responsibilities are distributed. Is clarification the responsibility of the author or the reader? What can I assume "a reasonable reader" should know? The answer depends on many things. But the right answer isn't a blanket policy of assuming incompetence and explaining every detail with comments or intermediate variables.
- marcosdumay 3y agoWell, none of that depends on your functions being point-free or you writing their parameters down. The Haskell culture in particular has a very strong idea of naming all the things that can have a good name, and none of the ones that can't. Point-free functions are about that last part, but you only touched the first part. (Personally, I think the culture is too radical, but you can't really argue against a principle like this.)