Holy cow. That is difficult.
You failed. Between the unreadable text colors and the word wrap, the code is incomprehensible. I cut and pasted it into a plaintext notes app and it was way easier to understand
1) replacing a func with a simpler recursive func may or may not be ideal.
2) this fills me with no joy when I read it:
> It seems easier to start from scratch as opposed to refactor it. The characterization tests above will help ensuring that the returned hash map stays the same.
Its enormously hmm… what word to use. Ambitous? Foolish? Brave?
Anyway, taking code, writing some tests for it and then rewriting the entire thing so it passes the tests you wrote only barely falls into “refactoring”.
If anyone refactored my code base by simply rewriting from scratch the parts they dont like, I would be upset.
Rewriting things is hard, and its … brave… to think you can just write a few tests and then if they all pass its all good!
That only works in very trivial examples, and really, doesnt show case the strengths of clojure at all.
Yes! After a few years of using Clojure for work; Fennel for configuring anything that requires Lua; Lisp-based editor, nbb and babashka for scripting — now I just can't deal with non-lispy languages without getting mentally and emotionally exhausted. It feels so taxing to have to deal with all the syntax elements, indentation, etc. So many times my thoughts got derailed due to a missing comma in some json or slightly misindented shit in yaml.
I just don't understand how I used to happily write programs before, and I did it for over a decade. Without ever being able to "transpose these two things around", "lift this structure and move it over here", "slurp this thing inside", "raise this expression", "wrap this piece into its own block", etc. And then the actual REPL — OMG, that's a song of its own accord.
Programmers with shallow understanding and lacking experience of Lisp (or straight bias against it) are missing out so much, it really is sad.
Lots of little, simple functions, composed... You can still use the threading at a higher function, but instead of doing the actual work at each step in the pipeline, you call a well-named function which does what that step was doing. Yes, it may feel redundant, but it absolutely improves human comprehension because it reads like clear human instructions.
Tests become much simpler too.
The only two challenges I face with this approach is 1 - naming things, and 2 - organizing. The function names tend to become quite long, since they ideally describe what the function does.
I haven't done this lately, now that the AI tools have become pretty great; I can imagine that an AI tool would be an excellent knowledge base for these many functions, and it would probably be very successful in providing additions or modifications given the clean, clear function names and brief contents.
That should always be how code is written, including inside of functions, which is the real problem here. Something absent from the original code that is needed in a case like this isn't more functions, it's comments and good names.
Pulling something out into a function that is purely individual application logic without intent for reuse is not the right way to use a function, whose purpose is to be called, ideally somewhere else and not just once. As Ousterhout points out in his book, proper abstractions hide information and implementation, they aren't just scaffolding that increase the surface of an interface without doing anything.
gleenn•5h ago
sammy0910•5h ago
NightMKoder•3h ago
Explicit dynamic bindings are better if you need something like this since those are thread local.