Advent 2019 part 3, `every-pred` and `some-fn`
source link: https://lambdaisland.com/blog/2019-12-03-advent-of-parens-3-some-fn-every-pred
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
This post is part of Advent of Parens 2019 , my attempt to publish one blog post a day during the 24 days of the advent.
Ah clojure.core
, it’s like an all you can eat hot-pot. Just when you think
you’ve scooped up all it has to offer, you discover another small but delicious
delicacy floating in the spicy broth.
In exactly the same way I recently became aware of two functions that until now had only existed on the periphery of my awareness. I’ve since enjoyed using them on several occasions, and keep finding uses for them.
These are every-pred
, and some-fn
. I’ll demonstrate them with an example.
Say you have a discussion forum. People can be admins or moderators, and besides
their username they can optionally set a nickname.
(def people [{:name "Elsie" :admin? true :mod? true} {:name "Lin Shiwen" :nickname "Rocky" :mod? true}])
If you want to grab people’s nickname if they have one, or fall back to their
regular name otherwise, then this is pretty elegant with some-fn
.
(map (some-fn :nickname :name) people) ;; => ("Elsie" "Rocky")
If on the other hand you want to find everyone who’s both an admin and a mod,
then you can do so easily with every-pred
.
(filter (every-pred :admin? :mod?) people) ;; => ({:name "Elsie", :admin? true, :mod? true})
Clojure has a bunch of functions with every
and some
in their names, which
can be a bit confusing. In some?
and some->
it refers to a value being
something other than nil
, whereas in some
and some-fn
it means “the first
one that applies”.
I think this overloading is part of the reason why these two have eluded me so
long. Only recently did I have the aha insight that while their names are very
different they are really each other’s complement. One combines functions with a
logical or
, the other does so with a logical and
.
(map #(or (:nickname %) (:name %)) people) ;; => ("Elsie" "Rocky") (filter #(and (:admin? %) (:mod? %)) people) ;; => ({:name "Elsie", :admin? true, :mod? true})
So next time you find yourself writing an anonymous function like this, consider
reaching for some-fn
or every-pred
instead.
The only difference is that every-pred
coerces its result to a boolean, making
it a little less general purpose. Say we had an every-fn
instead, you could
combine it with some
to get for instance the name of the first admin in the
list.
(some (every-fn :admin? :name) people)
The implementation of every-fn
is left as an exercise to the reader. (hint:
it’s just like every-pred
, but without the calls to boolean
).
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK