Fork me on GitHub
#clojure
<
2024-02-04
>
Luke Zeitlin17:02:24

Kinda bemused by this: https://github.com/unclebob/functor. It's not clear to me what problem it solves.

Ben Sless18:02:27

if you dislike letfn or miss scheme's nested defines

Luke Zeitlin18:02:49

so it's purely aesthetic?

Luke Zeitlin18:02:48

I never seem to use letfn but nested functions seem to work fine. Does this look better?

Ben Sless18:02:09

Not purely, there's also juggling of variables that get lazily initialized and referenced later

Luke Zeitlin18:02:27

Why not extract a function that returns the value and use it to initialise the variable in the outer scope?

Ben Sless18:02:47

tbf I think it looks better but it does other things which I'm not a fan of.

phill18:02:01

Reminds me vaguely of Plumatic Plumbing (which, by contrast, was genuinely useful)

Ben Sless18:02:15

Why not extract a function? that's exactly what I said https://gist.github.com/bsless/8b2572df1f9ffe9dfc8465ca417c9e2e

Ben Sless18:02:19

It's useful if one person is using it, and I believe Bob decided to use his own library for his own projects, so there's that

phill18:02:42

There is a history of alternative 'let' forms.. But the quest is a difficult one, because each more-beautiful 'let' adds complexity

Ben Sless18:02:03

It's nice that he's happy with it but I don't think it's the best idea. This one adds complexity, hidden control flow, hidden allocations

👍 1
1
p-himik18:02:31

Out of curiosity - how did you lean about functor? Was it announced somewhere? Certainly not here.

Luke Zeitlin18:02:04

came up on the home feed on github

Ben Sless19:02:36

Uncle Bob's twitter It turns out with some effort you can cultivate a feed focused mostly on programming 🙃

cjohansen19:02:55

Even when you've chosen to include Bob in it? Impressive

Luke Zeitlin20:02:40

Ah programming twitter... mine is awash with ocaml and htmx right how (both seem cool but not sure why they everyone suddenly got exited about them in unison). Still, it breaks up the relentless rust advocacy

Noah Bogart20:02:16

Ocaml is getting a lot of focus because tj devries and theprimeagean are both talking about it a lot

shay23:02:44

We already know Bob’s opinions about human beings show him to be quite stupid. Why do we expect him to fare any better on other topics?

wevrem05:02:38

@U42AKMNUC I’m actually ignorant of whatever offensive thing Bob said or did, but at the same time I’m not impressed by your intolerant tone nor by you calling someone “quite stupid”. That sort of language doesn’t seem to be necessary.

💯 3
oyakushev08:02:08

It's a matter of taste if you like the look of it or not, but it is objectively harder to REPL-debug compared to defining those nested functions in top level and adding some commented examples for each.

oyakushev08:02:51

Bob is a questionable character alright. Having such a prominent proponent of the language is a boon, but after all these years of being enamored with Clojure, he still misses the primary strength of the language.

💯 3
shay09:02:05

@UTFAPNRPT considering the volume his bigoted remarks were made at, being ignorant is a choice in itself, one I wouldn’t be as proud as you are in declaring.

shay09:02:45

Look what happened to the Scala community, when they refused to kick out their racists supporters: they made everyone else run away.

Ben Sless12:02:44

@U06PNK4HG with all that in mind, mad respect to someone who's a veritable dinosaur of the industry and a prominent voice who's willing to say "you know what, I've been doing it all wrong for decades". Yeah, I wish he'd embrace the dynamic DX of Clojure, but sometimes, such is life

👍 1
p-himik13:02:15

Personally, I find that it's not that useful to have every single piece of an app to be REPL-friendly. Lots and lots of things are relatively simple and fit into my head just fine - if I experiment with them, it's only at the boundaries of the whole system, and there the reloaded workflow works perfectly for me. E.g. compose an SQL query given the user input, connect to the DB, retrieve the data with that query, transform it, schedule some tasks, send some notifications, send the data itself - all trivial, not that far from composing a few transducers in terms of cognitive load. Zero need to experiment. Just a fraction of the code that I deal with on a daily basis deals with things so complicated and complex that I can't not experiment with it interactively during development - those parts do have to be REPL-friendly, at least during the main development phase when there's more churn in that code. E.g. load an XML with a non-trivial schema that encodes data that must be accessed sequentially in order to accumulate values along the way, traverse it only to the required point and accumulate all the necessary things in a type-specific manner, encode the result in a different format. When just the amount of the different kinds of things being accumulated is enough to make my head boil, a gradual experimentation process is most helpful.

1
cjohansen13:02:20

The problem with this approach is that you have to know upfront which parts of the system you may need to interact with at any given point. Given enough time, I'll want to REPL my way through basically anything, so I make sure that as much as possible can be easily used from a REPL. Code easily accessible from a REPL is also easily testable 👍 > E.g. compose an SQL query given the user input, connect to the DB, retrieve the data with that query, transform it, schedule some tasks, send some notifications Not being able to do any of these from a REPL seems to me to miss out on one of the greatest benefits of the workflow Clojure enables. To each their own I guess 🙂

p-himik13:02:32

"Making the code easily accessible from a REPL" is not the same as "making the code REPL-friendly". With Integrant, I can access every part of the system from REPL, it's not a problem at all. > Not being able to do any of these from a REPL seems to me to miss out on one of the greatest benefits of the workflow Clojure enables Same here - it's a completely different argument. I can access everything from REPL. I do access all sorts of things from time to time. And I can reproduce the behavior that I'm interested in in a REPL session. But I don't write REPL-friendly components the vast majority of the time. E.g. I almost never use #', I don't need to even think in that direction. In many, many videos on "REPL-driven development", you'll see things like "let's run this function and see that its result is indeed what we expect, now let's filter its output and check the result, now let's map over it and check the result, now let's do X and check the result," ad nauseam. And those examples usually end up being something like (into [] (comp (map #(* 2 %)) (remove even?)) (range 100)). If you know what every function in that form does on a valid input, you don't need to eval the form to know that it results in an empty vector. So with such similarly trivial code (and most of the code that I write is trivial in this sense), why would you want to write it in an interactive and iterative way? Why not just write it from the get go, confirm that it works once, and call it a day? Even if you wrap the whole thing in a function factory, there's still no need to use #' since you know what it does and you know that it will never need to be changed in a piecemeal manner.

1
cjohansen13:02:57

Ok, I probably misread you then. I'm not sure I understand your point completely, but it looks like we agree 😄

p-himik13:02:01

People often conflate being able to do anything you want in a REPL with developing in the most iterative way possible. Those are not intertwined. The former improves the latter, that's it. But if I use the former, I don't have to do the latter. I personally don't, I dislike such an approach very much, I find it distracting. I don't want to think which parts of my code were affected by me redefining this particular var - I only want to see how it all works together after the change, and the "reloaded" workflow is fantastic here (but I trigger it manually - I've seen instances where people dislike that workflow because they assume that it's fully automatic. It's not, it's manually triggered).

🎯 1
p-himik13:02:42

To tie it all back to what Bob has said: > I'm not too concerned about the mental overhead for small functors. Large functors should be avoided, period. > [...] so long as functors are small tests should be conducted through the main functor rather than the individual subfunctions. And that's exactly what I mean as well - there's no need at all to run a small well-defined function that you grasp at a glance in your REPL to confirm what it does. You already know what it does. There's of course always a tiny chance that something's wrong - maybe your understanding is wrong, maybe you've underslept, maybe the input is bad, etc. But you'll notice it immediately upon checking the outer function that uses all those small functions. It's the same deal with unit tests - I dislike those very much, at least when by "unit" people understand "the tiniest thing that could possibly be tested". It ends up being a race to the bottom with yourself or your colleagues, with dumb "abstractions" like adder or mapper or whatnot and things that aren't that far from (assert (= (+ 1 2) 3)).

Luke Zeitlin14:02:25

regardless of repl testing, this bob comment above points to why I personally cannot see a use-case for functors. Functors are supposed to simplify interdependent, nested functions, but they quickly become complicated at scale. The small, simple examples that he gives in the readme become more verbose and (subjectively, imo) harder to read with functors, I prefer the vanilla Clojure approach (although it could be factored better than the code he has given). That was the original intention of the post. I wanted to see if I was missing something, what the killer app is.

cjohansen14:02:47

I didn't care for the functor abstraction at all. You could redo it with regular functions, and it would be accessible in a REPL, easier to read, and would not require knowledge of a third-party library to understand. His library breaks several conventions for no apparent upside: Code is used before it is defined, functions are called without parameters, code can't be easily debugged, helper functions are not available outside the main function etc. Entirely pointless in my eyes.

Luke Zeitlin14:02:13

tricky for static analysis to figure out what's going on too I expect

misha16:06:54

(macroexpand-1 '
  (blet [discr        (- (* b b) (* 4 a c))
         i-sqrt-discr (Math/sqrt (- discr))
         c-x1         [(- b) i-sqrt-discr]
         c-x1         (map #(/ % (* 2 a)) c-x1)
         c-x2         [(- b) (- i-sqrt-discr)]
         c-x2         (map #(/ % (* 2 a)) c-x2)
         sqrt-desc    (Math/sqrt discr)
         x1           (/ (+ (- b) sqrt-desc) (* 2 a))
         x2           (/ (- (- b) sqrt-desc) (* 2 a))]
    (cond
      (zero? a)     (/ (- c) b)
      (zero? discr) (/ (- b) (* 2 a))
      (neg? discr)  [c-x1 c-x2]
      :else         [x1 x2])))

=> 

(if (zero? a)
  (/ (- c) b)
  (let* [discr__49358 (- (* b b) (* 4 a c))]
    (if (zero? discr__49358)
      (/ (- b) (* 2 a))
      (if (neg? discr__49358)
        (let* [i-sqrt-discr__49359 (. Math sqrt (- discr__49358))
               c-x1__49360         [(- b) i-sqrt-discr__49359]
               c-x1__49361         (map (fn* fn__49367 ([p1__49354#__49368] (/ p1__49354#__49368 (* 2 a)))) c-x1__49360)
               c-x2__49362         [(- b) (- i-sqrt-discr__49359)]
               c-x2__49363         (map (fn* fn__49369 ([p1__49355#__49370] (/ p1__49355#__49370 (* 2 a)))) c-x2__49362)]
          [c-x1__49361 c-x2__49363])
        (let* [sqrt-desc__49364 (. Math sqrt discr__49358)
               x1__49365        (/ (+ (- b) sqrt-desc__49364) (* 2 a))
               x2__49366        (/ (- (- b) sqrt-desc__49364) (* 2 a))]
          [x1__49365 x2__49366])))))