Clojure: `every-pred` and `some-fn`
lambdaisland.com
lambdaisland.com
As a personal name convention, I use '|' (the piping character) as a suffix to name any function returning a function, which includes function combinators. Thus the naming of these functions becomes straighforward: 'every-pred' becomes 'and|' while 'some-fn' turns into 'or|'.
Example:
(filter (and| pos? even?) nums)
(filter (or| neg? odd?) nums)
More: https://gist.github.com/TristeFigure/acd689f3c57e840ebb9f8a6...Other oddities include juxt, areduce, fnil, nthnext, derive, seque, and by now arguably the whole "ref" memory system (since Software Transactional Memory has pretty much gone by the wayside as a desirable programming feature)
Of course, Rich Hickey has also been very good at deciding which features to put into the language in the first place, so the number of legacy oddities is pretty tiny anyway, fewer than most languages.
"some" and "every" create nicely readable code. "every-pred" and "some-fn" could be called "satisfies-every" and "satisfies-some", respectively.
https://hackage.haskell.org/package/lens-4.18.1/docs/Control...
https://gist.github.com/ChrisPenner/1f7b6923448b3396a45d04a2...
(->> people (filter :admin?) (filter :mod?))
This is admittedly a matter of taste, though.For the some-fn example, I would argue that the usage in OP is a very idealized and tuned example... how often do you really want to say "try this one predicate and if that doesn't work, try this other one"? I only need to do that maybe a couple of times a year (when parsing some sort of irregular raw data) so it's rare enough that I'd prefer to write the predicate explicitly instead of using this sort of succinct predicate composition that could become a "head-scratcher" at a future date.
(filter (every-pred :admin? :mod?) people)
is that a) this arguably conses less, by building only one results list, and b) :admin? and :mod? are just regular arguments, which means they could be replaced by data. I'm thinking of: (let [acl [:admin? :mod?]]
(filter (apply every-pred acl) people))
This capitalizes on Clojure's interesting design decision in which when funcalled, a :keyword resolves to function that gets an element with the name :keyword from the map passed as the argument.Aren't lazy sequences idiomatic in Clojure?
(filter (and| :admin? :mod?)
people)
and: (filter (or| :admin? :mod?)
people) (into []
(comp (filter :admin?)
(filter :mod?))
people)
As a side-effect, they are also more generic so you can reuse the transducer stack if you decide to use core.async or something.I’d also be a bit careful about judging popularity from open source codebases too: they are used heavily in all the production code based I maintain :) and I get the sense that they’re pretty widely used by the “silent majority” of clojure programmers.
My debugging strategy for them is basically what I do for threading macros anyways: (into [] (comp ... (map #(doto % (->> (println :it))) ....) inp)
This is pretty severely mis-stated
He believes the core of a dynamic programming language (specifically the core) has a very high bar for stability in order to avoid a number of problems that other dynamic languages have. This intersects with a number of other ideas, for example Lisp macros can extend syntax from a library and thus keep the change-restricted surface area small since almost anything can be implemented in a library.
I think, don't recall a smoking gun quote for this, correct me if I'm wrong.
As far as his example goes, this IS how you model data:
(def people [{:name "Elsie"
:admin? true
:mod? true}
{:name "Lin Shiwen"
:nickname "Rocky"
:mod? true}])
Notice the lack of a "fill-in" static ADT form. No types here -- no static types NOR no ":type" field. Instead, we say what we know about our data items and we omit what we don't know. Just the facts, ma'am. And then Clojure gives us the obscenely simple tools to deal.It will forever confound me why some developers/PLs wish to model data in static, inflexible, a priori structures -- given the overtly clear development efficiency and simplicity of NOT doing so (and not HAVING to do so.)
For example, I personally love static types. In your example I would prefer modelling people as a list of structs, each one with a required "name" (string), an optional "nickname" (string option), and required boolean flags for mod and admin.
Why would I prefer it to be that way? Because a huge number of typos I might introduce in my project are caught before I even try to run something.
I've been bitten once too many times by a typo somewhere (like looking up the "nick" property instead of "nickname") and having the program raise an error after it's already ran for a while. I can't stand that.
Working on statically typed code bases that model things the way you are describing substantially increases the effort and time it takes to iterate. So while you may prefer them (per your experience or what else), there is still the fact that this way of development is suboptimal with respect to delivery throughput over time.
Of course this is arguable (and is a very heavily argued topic) and the common retort is "to each is own" that isn't sufficient to me.
One might prefer to work in assembly language (per experience or predisposition) but there is still a general statement that can be made that this is intrinsically a suboptimal way to work if delivery throughput is your goal.
I believe that a similar claim can be made to differentiate working in static ADT v. dynamic Map types when modeling business domains.
More food for thought. The typo problems you mentioned are solved by static types but at what cost? And is the CBA there? Especially considering that there are other ways to "solve" the typo problem that do not come with the heavy tax burden of statically typed ADTs.
I can make changes and find many bugs at compile time without writing tests. I still write tests of course but I have to write fewer, and I rarely waste time fixing trivial things that raise errors at runtime.
It is also much quicker for me to navigate the code. I can find definitions for identifiers instantly and get my mental model refreshed easily every time I switch contexts.
I use dynamic languages too, and I find myself spending more time on things that static languages do for me.
Despite spending my last few years working with Common Lisp and a bit of Clojure, I've grown fond again of type constraints. Typing your data prevents a lot of silly and easy-to-make mistakes. Spec in Clojure is doubly useful, because specs can serve both as tests and generators. So after you define how your data is supposed to look like, you can make Clojure start pumping out randomly-generated examples, which you can then use to test your code.
(Note that typing maps with Spec doesn't mean they're inflexible, a priori structures. You just pin down what you know. In this case, you could define that a "person" needs a :name key, and can optionally have :admin?, :mod? and :nickname fields. If it also has some other fields, no harm done.)
The way we've used it, it worked almost as if the maps were statically-typed, and it saved my bacon quite a few times.
(ns user
(:use clojure.set)
(:require [clojure.spec.alpha :as s]))
(s/def ::map-closed #(-> %
keys
set
(difference #{:a :b :c})
empty?))
(s/valid? ::map-closed {:a 1, :c 2})
=> true
(s/valid? ::map-closed {:a 1, :c 2, :e 3})
=> falseI haven’t used any of the “some” functions much, but it seems to me you could read this was “the first one that is not nil” which would be consistent.
[1] https://www.manning.com/books/clojure-the-essential-referenc...