Specter – Clojure API for immutable programming (2017)
nathanmarz.com
nathanmarz.com
Meander [1] is also wonderful. Its unification-based approach makes for nice expressions of intent. Its strategy-based approach to rewriting [2] is more flexible than most common walker libraries, too.
[0]: https://github.com/redplanetlabs/specter/wiki/Specter's-inli...
[1]: https://github.com/noprompt/meander
[2]: https://github.com/noprompt/meander/blob/epsilon/doc/strateg...
Some production code:
(defn transform-tagged-values [x]
(sp/transform (sp/walker tr/tagged-value?) tagged-value->cljs x))
Give it a predicate for which values to transform, and a function to do the transformation. Couldn't be easier.When I started using it in one of my existing projects I was able to completely eliminate a few needlessly long and complex functions that were littered with comments and calls to helper functions. In their place are functions that are about 5 lines of code that barely even need a docstring because it's readily apparent what they do just by looking at the source.
I was more trying to show that the API could be better and hoping this would put pressure on core devs to do it properly.
A great example of why macros in Lisp are powerful, and how they empower you as a user even if you never write one.
also cf
Glom for Python: https://sedimental.org/glom_restructured_data.html
Hashdig in Ruby's standard library: https://ruby-doc.org/core-2.7.1/Hash.html#method-i-dig
What... are.. you.. talking... about? Do some googling around. Clojure today is the most popular in its category of the languages with strong FP emphasis and slowly and steadily growing. It has already generated more jobs, books, active podcasts, conferences. More than Haskell, OCaml, F#, Elm, Purescript, Erlang, Elixir, Idris, Julia, and has recently surpassed Scala and become 3rd most used JVM language after Java and Kotlin.
Of course, one can say: "actually, none of these have gained much adoption in the industry", and speaking in relative numbers, they'd be right. But those who can no longer bear the pain of writing awful, deeply nested Java class hierarchies or tired of debugging concurrency issues would always find projects to write and sometimes even get paid for, using their preferred tool.
I honestly don't understand people, when they say: "It's really nice, I love it, but I'm getting paid to use something else." Well, do you really expect someone to call you and say: "Hey, I saw your comment on HN, it seems you'd love to use Clojure, can we offer you a lot of money?", or something like that?
I for one, after only a week of playing with Clojure, immediately quit my job. It took me just a few days to find a Clojure gig. I switched jobs three times since then, never (even during the COVID-19 crisis, when I was laid off) was a question for me "Is the fun over? Do I have to start looking for Python jobs now?" Luck of course is a factor, but you have to herd, guide and push your luck forward. It's no one's fault that you are forced to use something you don't like.
I'm almost 99% sure both jobs were places that bought into some dynamic developer who promised unicorns and rainbows and then left them with a huge clojure project have their eyes bleed to. No thanks.
It takes more than just a language to create a productive development environment in 2020, especially one which is so flawed by designed to the point of not even being statically typed.
Interesting point. Sadly, it seems engineers at Apple, Cisco, Walmart, NASA, CircleCI, Pandora, Grammarly, Funding Circle, Nubank and hundreds of other companies are amateurs who don't know jack-shit about challenges in the industry and keep building their sand-castles using their incredibly flawed tools. I'm sure, these losers would soon vanish from the radar and we would never hear about them. Just give it a few years.
I have been learning Clojure off and on for almost a year now. I gotta say that it has changed my mind to a "Aha, I got lisp now" moment. Working with it is a joy, especially it integrates so well with the JVM and all available Java libraries out there.
I can't believe I have missed Specter during my learning journey. How could I not find out about this soon enough. I mostly deal with static typed languages and seeing the primary use of map and keywords as main data structure scares me. Rich Hicky said not to put too much emphasis in what in the map but the data/keywords you need, kind of like you do not care what in the UPS truck as long as it could deliver your package, right? But, navigating maps and nested data structures around is the pain. Specter definitely easies that pain a lot now that I know it!
This is why I am grateful for hackernews. Once in awhile, browsing the site and discover something like this makes me rethink the ways I do stuff. It also reminds me that I could be missing a lots of similar goodies on my way learning Clojure. I am prepared to have me mind blown again!!!
Meander was the last one I can remember:
Keywords don’t form a DSL in Specter, they just do keyword navigation into maps, the same way ‘0’ navigates into the first element of a list.
This touches upon a really nice element of Specter, which is its pattern for reuse. Here’s a very real but deliberately simple example of using it:
(def chat-threads
{[“sova” “tekacs”]
[{:text [“hey” “you there?”]}
{:text [“line 1” “line 2”]}]})
(def ALL-TEXT
[sr/ALL :text sr/ALL])
(defn text-for [chat]
(sr/select [chat ALL-TEXT]))
(def ALL-CHAT-TEXT
[sr/MAP-VALS ALL-TEXT])
(text-for [“sova” “tekacs”]) ;=> [...]
Here the navigator into a list, then the :text field and then the list is bottled simply by putting several navigators in a vector.Then, it can be used alongside other navigators in a first class fashion — [chat ALL-TEXT] is just a nested vector. :)
{:all {:a 2} :foo {:a 4}}
Maybe namespaced keys would fit that usecaseI wonder, is it possible to select some subset of a map? Say you have these paths:
(def paths [[:some :path] [:another :deeper :path] [:yet :another :one] [:yet :another :one :deeper]])
And select from a map these paths. You could do it with get-in, but maybe Specter can do it faster? I couldn't find a way to do it with Specter...
They really are the key to make immutable data structures as easy to update and work with as mutable data structures.
This is the TXR Lisp interactive listener of TXR 243.
Quit with :quit or Ctrl-D on an empty line. Ctrl-X ? for cheatsheet.
Give me all the .c files: 1> (glob "*.c")
("abcd.c" "args.c" "arith.c" "buf.c" "cadr.c" "chksum.c" "combi.c"
"crazyffi.c" "debug.c" "dict.c" "eval.c" "ffi.c" "filter.c" "ftw.c"
"gc.c" "glob.c" "halfsip.c" "hash.c" "highest-bit-main.c" "highest-bit.c"
"highest-test.c" "isqrt.c" "itypes.c" "lex.yy.c" "lib.c" "lisplib.c"
"match.c" "parser.c" "protsym.c" "query.c" "rand.c" "regex.c"
"signal.c" "socket.c" "stream.c" "struct.c" "strudel.c" "sysif.c"
"syslog.c" "termios.c" "test.c" "tree.c" "txr.c" "unwind.c" "utf8.c"
"vecho.c" "vm.c" "y.tab.c")
OK, which ones are over 100000 bytes: 2> (keep-if [chain stat .size (op < 100000)] (glob "*.c"))
("arith.c" "eval.c" "ffi.c" "lex.yy.c" "lib.c" "match.c" "stream.c"
"y.tab.c") 1> (defstruct node ()
item
next)
#<struct-type node>
2> (new node next (new node next (new node item 42)))
#S(node item nil next #S(node item nil next #S(node item 42 next nil)))
3> (call .next.next.item *2)
42