Fork me on GitHub
Cris B00:08:24

Anyone know of Clojure data browsers usable on hidpi screens? Thus far I've tried clojure.inspector, REBL and Reveal, none of which do UI scaling, so are unreadable on my laptop screen. Portal (which uses a browser-based UI) is one possibility I've yet to look into. Are there any others?


doesn't REBL use a tty? it would scale if the TTY scales


oh never mind, I had it confused with rebel-repl

Cris B00:08:02

Ah - yes it's JavaFX. Java apps in general seem to have the most difficulties with hidpi (it took IntelliJ years). I'm on Linux (Wayland/Gnome)

Cris B00:08:41

Thanks - I've played around with various env vars & java args, but not very systematically. I'll have a further look


for most things like this, it doesn't make sense to look for clojure specific options, any fixes will be jvm level


(unless you take the browser based route, of course, then it's browser level :D)

Cris B00:08:57

Not clojure-specific, but app-specific. I don't know java UI kits, but I believe (for example) JavaFX needs some work in the app code to allow UI scaling, and maybe REBL doesn't have this.


I'd be very surprised if that were true

Cris B00:08:41

Well I'll have a further look into it. Reveal ( looked promising as its UI is terminal-ish, but it turrns out that the 'terminal' is a JavaFX app ;(

Cris B00:08:01

@noisesmith OK, adding :jvm-opts ["-Dglass.gtk.uiScale=200%"] to the alias works for Reveal. So that's at least one up & running, thanks.


that's great, tbh I have just been changing my linux screen to a lower DPI with xrandr, so it's interesting to consider that better behaved apps would make it unneccessary


@cb.lists You're probably already aware but there's a #reveal channel and @vlaaad is pretty receptive to feedback so that might be something he could add to the README?


(I just started using Reveal with Atom/Chlorine and it's pretty nice -- but doesn't yet have a lot of the fancier datafy/nav stuff that REBL has around Vars and namespaces that let you browse code and call trees)

Cris B00:08:05

@seancorfield - I hadn't spotted the channel. Thanks I'll point the hidpi thing out there. It was one of your screencasts that alerted me to the niceties of using a data browser, so thanks for that too.


Oh cool! I'm hoping to do some more soon. Just haven't found much time lately.

🤞 3
Cris B00:08:47

Please do if/when time allows - I tend to be more a reader than viewer, but found your previous screencasts really clearly expressed.

Cris B00:08:29

@noisesmith - yes I also use a lower res when I have an external screen attached, but I really enjoy the clarity when on the unadorned laptop.


I have the dell xps15 with the 4k touch screen, I like it a lot, I just wish there were more apps that let me take advantage of multi-touch


but open-stage-control is enough to make it worth it on its own, for my hobby usages

Cris B01:08:45

Same machine! I felt I needed a good screen having got used to hidpi with previous macbooks, and it hasn't disappointed in that respect. I haven't found myself using the touch a great deal - I don't think anything I do really demands it.


I think audio controls are the perfect multi-touch use case

Cris B01:08:12

Yep I can see that it would be.


I ended up needing to make some scripts to adjust the touch screen mapping when using external monitors though (default behavior is beyond useless - it maps the built in screen across the whole display)


I mean, try to make it less obvious you're just reusing absolute mode touchpad code haha


Hi guys, how does one use areduce ? Am doing this below but getting illegal argument.

(defn cal-armstrong [arr]
    (areduce arr idx ret 0
             (+ ret (aget arr idx))))

  (cal-armstrong [1 2 3])
Am trying to do a reduce to do something like multiplying each of the elements by its index and then summing them


[1 2 3] is not an array


I kinda just realised. So all the a-something methods are for java arrays isit like areduce amap ?


right - "java arrays" are arrays (on jvm clj), there isn't another kind


Got it!. How might I get use the position of elements in a list as an argument in a reduce then?


user=> (let [a (into-array [1 2 3])] (areduce a i x 0 (+ x (aget a i))))


user=> (reduce (fn [acc [idx x]] (+ acc (* idx x))) 0 (map-indexed list [1 2 3]))


map-indexed can easily construct that


Right, I saw it but didn't understand how i might be able to use reduce on it! Thanks!


the feature to look at to understand that fn arg list is called "destructuring"


of course you could do it (more verbosely) without destructuring - this is sometimes needed for performance


(ins)user=> (reduce (fn [acc pair] (+ acc (* (first pair) (second pair)))) 0 (map-indexed list [1 2 3]))
(ins)user=> (reduce (fn [acc pair] (+ acc (apply * pair))) 0 (map-indexed list [1 2 3]))
but as I show in the second example, higher order things like "apply" can help too


Ahhh, okay I didn't think about being able to using destructing here o: but yeap got it now!


it's quite common to use destructuring in the first reduce arg as well (when you have more than one state datum to track)


there's also loop (at the drawback of needing to do first/rest to traverse by hand)


rightt! Thanks for your help! Quite abit to process but got it. Definitely need more practice and exposure


user=> (loop [acc 0 idx 0 [el & els] [1 2 3]] (if el (recur (+ acc (* idx el)) (inc idx) els) acc))


IMHO when first beginning these kind of refactors are useful


(to solidify your comprehension of the idioms)


Roger that! Had a small bit of that from going for loop to reduce to having apply in a exercism qn. Doing another one now but was stuck for quite awhile hence me being here

Cris B04:08:35

I notice doall is often used in clojure.core in nonobvious (to a beginner) ways. For example this extract from partition:

[n step coll]
       (when-let [s (seq coll)]
         (let [p (doall (take n s))]
           (when (= n (count p))
             (cons p (partition n step (nthrest s step))))))))
I think I understand what it does, but not why it's used here, given we're building a lazy sequence in any case.


The laziness is not in the individual partitions but the creation of all of them.

Cris B04:08:36

What I'm not clear on is why realize p here?


Given that there's a call to count on p, I'm a bit surprised to see the doall as well...

Cris B04:08:46

Would it make sense were there no count or other realising function before the cons?


Yeah, if you didn't have count, you'd definitely want doall, otherwise you're stacking up a lot of laziness that could blow the stack I suspect...


(you've seen Stuart Sierra's "Do's and Don'ts" that covers concat?)


I don’t see how this could blow the stack

Cris B04:08:46

@seancorfield - no, I'll look it up


If nothing realized p you'd have a whole sequence of lazy take n calls -- and if coll itself was lazily produced, you'd have stack frames building I think @dpsutton?


The nested takes are on nthrest which should be unaffected by whatever you do to the first take I would think


It's a bit late in the evening for my brain to reason about that sort of nested laziness (without realizing each p chunk).


Agree :) I’m not confident in my answer either. Been a few weeks since I traced down how the lazy seqs are views onto the underlying collection. Much easier in cljs too


I think the compiler can turn that doall into something that consumes less stack, compared to the result of take


remember that the compiler doesn't try to be especially clever - you could in theory prove that the count meant the coll was realized, but doall at the binding site is much easier to detect and dispatch on

Cris B09:08:54

Though to 'remember' that a poor clojure beginner would have had to know something about the clojure compiler in the first place .Some of us (in this channel at least) are still sweating through 4Clojure exercises remember!


haha, that's fair, the clojure.core functions contain performance optimizations that are non obvious (and usually not documented), and it's a style of programming that is only called for because everyone uses those functions, so the optimizations are worth it to speed up the whole language


it's a style of programming that even experts should avoid, unless we can prove that the optimization is needed


and definitely not something for a newcomer to reach for

Cris B21:08:59

Yep of course - same is true of libraries in other langs eg. lots of unsafe in Rust std. Interesting to dive into however superficially to see what's going on though.

Jim Newton08:08:35

I'm confused about how create a map (hash map) in a similar manner as creating a sequence using mapcat. When using mapcat if a certain iteration decides to not contribute any elements to the sequence being construction, the iteration function can simply return nil and nothing gets concatenated for that iteration. Can I write the following more efficiently? or more readably. personally I dread trying to understand this code after 1 month.

(into {} (letfn [(some-function [id]
                  (if (> id 5)
                    [[id (cl-format false "~R" id)]]))]
           (mapcat some-function (list 1 2 10 11 -1 -2))))
which returns the map, with no contribution when (> id 5)
{1 "one", 2 "two", -1 "minus one", -2 "minus two"}

Ben Sless08:08:24

How about using a transducer? I also don't see the need for mapcat here at all

  (filter #(<= % 5))
  (map (fn [id] [id (cl-format false "~R" id)])))
 (list 1 2 10 11 -1 -2))

Jim Newton09:08:05

actually in my case that would make it more complicated.

Jim Newton09:08:31

in my actual case I don't have a simple predicate of the input, but rather I need to call a function f on the input, test the output of f, and decide whether to contribute to the sequence being built. If I indeed need to contribute then I need to use the output of f again to construct the list contribution.

Jim Newton09:08:36

something like the following.

Jim Newton09:08:56

(into {} (letfn [(some-function [id]
                  (let [x (f id)]
                    (if (some-condition x)
                      [[id (cl-format false "~R" x)]])))]
           (mapcat some-function some-sequence))

Jim Newton09:08:42

sorry the parens aren't correct

Ben Sless09:08:34

So you need to build your transducer like: (comp (map f) (filter p)) To add further processing steps: (comp (map f) (filter p) (map (fn [in] [in (g in)])))

Ben Sless09:08:14

If you need to retain the original element there might be some more hand waving involved

Ben Sless09:08:34


 (map (fn [in] {:in in :res (f in)}))
 (filter (comp pred :res))
 (map (fn [{:keys [in res]}] [in (g res)])))

Jim Newton09:08:06

I'm not sure I understand your suggestion, but it is already more complicated than simply using mapcat

Jim Newton09:08:48

The thing I don't like about the mapcat solution is that I have to create a sequence and then convert it to a map rather than just creating the map as I go.

Ben Sless09:08:13

Ah! So why not use reduce?

Ben Sless09:08:32

 (fn [m id]
   (let [x (f x)]
     (if (p x)
       (assoc m id (g x))

Jim Newton09:08:39

ahh, so that's sort of like building my own mapcat for hash maps.

Ben Sless10:08:21

sort of. Reduce is general enough to suit your needs.

👍 3
Jim Newton10:08:40

BTW, I taught a course of Scala this semester, and the students had a lot of homework problems using fold and foldLeft to compute bizarre things. I wanted them 1) to understand how to iterate without mutation, and 2) that lots of problems can be re-imagined as a fold/reduce operation.

Jim Newton10:08:07

in the student feedback at the end of the semester, several of the students commented about how powerful the fold function is.

Ben Sless12:08:32

Whenever I get my hands on new employees I have them implement reduce themselves, show the equivalence to tail recursion, and let them implement map and filter using reduce. A bit of a crash course approach but they also commented on how informative it was


into doesn't require mapcat for building maps btw

(into {}
      (map (fn [i]
             (when (> i 5)
               [i i])))
      (range 12))
{6 6, 7 7, 8 8, 9 9, 10 10, 11 11}


note that map doesn't contain the sequential input here, into can build a hash map with less intermediate garbage compared ot reduce (and for special inputs like range or vectors, can create less garbage while walking its input as well)


using map directly on the range would undermine both of those features


the idiomatic version of your original:

(into {}
      (map #(when (< % 5)
              [% (cl-format false "~R" %)]))
      [1 2 10 11 -1 -2]) 

Jim Newton10:08:22

is there some sort of function in the clojure library which accompanies group-by and post-processes the values generated? I call it group-by-mapped Whereas (group-by first '([1 10] [2 20] [1 100] [3 30])) returns {1 [[1 10] [1 100]], 2 [[2 20]], 3 [[3 30]]} I often want to compute the following instead {1 [10 100], 2 [20], 3 [30]}... often the key of the computed map is repeated in the hash map values, of course depending on the function passed to group-by

Jim Newton10:08:00

This is what I end up writing

(defn group-by-mapped
  [f1 f2 coll]
  (into {} (map (fn [[key value]]
                  [key (set (map f2 value))]) (group-by f1 coll))))

Jan K11:08:24

You could use group-by + map-vals from the popular medley library

👍 3
Jim Newton11:08:55

The documentation for assoc does not say what happens when it's first argument is a record. But it happily does the right thing.

(map->MyRecord (assoc old-record :f1 new-f1 :f2 new-f2))
Is this safe to depend on? Because it's awfully useful.

Alex Miller (Clojure team)12:08:39

You don’t even need the map->MyRecord, it’s still a record

Jim Newton12:08:25

@alexmiller, interesting. So how does print-method know how to distinguish them from other maps which magically have the same keys?

Alex Miller (Clojure team)12:08:03

Printing switches on type

Jim Newton12:08:20

yes but you said the record is a map. so it's type is map, right? how can print-method switch on its type if its type is map?

Alex Miller (Clojure team)12:08:27

maps are defined by interfaces (abstractions). many things can implement the interface while having their own type.

Alex Miller (Clojure team)12:08:07

for example, there are 2 concrete map implementations (PersistentHashMap and PersistentArrayMap). the main "map" interface is IPersistentMap (although there are other interfaces for subsets of the functionality like ILookup and Associative)

Alex Miller (Clojure team)12:08:37

all defrecords implement the IPersistentMap interface

Jim Newton12:08:35

BTW. that records are maps, is that just a useful leaky abstraction, or is it guaranteed as part of the design?

Alex Miller (Clojure team)12:08:34

It leaks in a couple places intentionally - the two big ones are empty and dissoc - when you remove “required” keys from a record it turns into a regular map

👍 3

Hi, someone can help me. I have this problem try to use next.jdbc

{:execution-id 1, :stage :enter, :interceptor nil, :exception-type :java.lang.RuntimeException, :exception #error {
 :cause "No suitable driver"
 [{:type java.lang.RuntimeException
   :message "Failed to get driver instance for jdbcUrl=jdbc:"
   :at [com.zaxxer.hikari.util.DriverDataSource <init> "" 114]}
  {:type java.sql.SQLException
   :message "No suitable driver"
   :at [
This is my component
(defrecord DatabasePostgress
  [option conn]
  (start [component]
    (if conn
      (let [conn (connection/->pool HikariDataSource option)]
        (assoc component :conn conn))))

  (stop  [component]
    (if conn
          (.close conn)
          (catch Exception e
            (prn "Error while stoping database")))
        (assoc component :conn nil))
And I try to use jdbc/execute! or something does anyone have any idea how i can do it?


this is my config map

(def db {:dbtype "postgresql"
         :dbname "example"
         :username "postgres"
         :password "postgres"
         :host "localhost"
         :port 5432})


Do you have the postgresql driver on your classpath?


Thanks.. I did have...


now worked well.. thanks

Jim Newton12:08:53

I have a loading namespace problem with records. I've defined a record with (defrecord Dfa ...) in a namespace dfa. And immediate after (for debugging) I've defined a function (defn record-name [] Dfa). In another name space, I've defining a function which calls (instance? dfa/Dfa obj). This fails at load time with an error Caused by: java.lang.RuntimeException: No such var: dfa/Dfa However, If I replace dfa/Dfa by (dfa/record-name) it works just fine.

Jim Newton12:08:52

I.e., I can call the parser understands the function call dfa/record but not the record name itself dfa/Dfa

Alex Miller (Clojure team)12:08:55

what's your ns declaration in the second namespace?

Jim Newton12:08:00

are record names var-ified later than function names?

Alex Miller (Clojure team)12:08:25

I don't think record names are var-ified at all? they are java types


that's right, you can't do (instance? dfa/Dfa ...) , must either import the record's class or use the fully qualified name of the class

Jim Newton12:08:37

so how does (instance? record-name obj) work? the clojure parser is able to resolve record-name at parse time. right. I admit I dont understand the parser very well.

Jim Newton13:08:54

I don't completely understand the question, but my main ns declaration is:

(ns clojure-rte.core
  (:require [clojure.set :refer [union]]
            [clojure.pprint :refer [cl-format]]
            [ :refer [cl-cond]]
            [clojure-rte.util :refer [with-first-match call-with-collector
                                      visit-permutations rte-constantly rte-identity
                                      print-vals sort-operands member]]
            [clojure-rte.type :as ty]
            [clojure-rte.dfa :as dfa ]
and the code in question follows:

Jim Newton13:08:09

(in-ns 'clojure-rte.core)

(defn dispatch [obj caller]
  (cond (instance? dfa/Dfa ;; (dfa/record-name)

        (sequential? obj)

        (throw (ex-info (format "invalid argument for %s, expecting, sequential? or Dfa, not %s"
                                caller obj)
                        {:obj obj

Jim Newton13:08:01

@ghadi, how can I compute the fully qualified name of the class?


bar=> (defrecord Foo []) bar.Foo


if you're in a namespace called bar, and you make a record Foo, the name is bar.Foo (it's printed when you evaluate at the REPL)

Jim Newton13:08:41

if the clojure name contains - what's the name to use?


try it at the REPL is my best advice


qualified symbols foo/Bar can never refer to classes


only simple symbols


foo.Bar (unimported class) Bar (imported class)


the other piece to tie all this knowledge together is that defrecords and deftypes make classes

Jim Newton13:08:00

clojure-rte.core> (type (map->Dfa {}))

Jim Newton13:08:19

when do they make classes? at evaluation time, or after the ns finishes loading?

Jim Newton13:08:53

I think the concept "ns finished loading" doesn't make sense.


ns'es can grow

Jim Newton13:08:13

I'm defining the record as follows:

(ns clojure-rte.dfa
  "Definition of records State and Dfa."
  (:require [ :refer [cl-cond]]
            [clojure-rte.util :refer [fixed-point member]]
            [clojure.set :refer [union difference]]

(defrecord Dfa [pattern canonicalized states exit-map combine-labels])

(defn record-name [] Dfa)
So In another ns which requires clojure-rte.dfa :as dfa which argument should I use with (instance? dfa/Dfa obj) ? which should work the same as (instance? (dfa/record-name) obj)

Jim Newton13:08:11

I really don't understand why clojure thinks the type is clojure_rte.core.Dfa rather than clojre_rte.dfa.Dfa

Noah Bogart14:08:38

if i have a record Foo (defrecord Foo [a b]) and I want to store a reference to it in an atom so I can do something like a factory, do I have to put the ->Foo constructor into the atom? I want to take a keyword and say "give me the record corresponding to this keyword"


sounds like you want a map containing many instances of Foo in an atom?


what would putting the constructor in the atom do?


the constructor is a function, it's a singleton, and it gets replaced when the record is redefined, I don't understand why you would put it inside an atom

Noah Bogart16:08:56

more like I have many records: Foo, Bar, Baz. and then I have a bunch of vectors that say [:foo 1] and [:bar 2 :baz 3]. i have a function that splits the vector into pairs (`[[:bar 2] [:baz 3]]`). i want a function where i take the resulting pair and then create the appropriate record: [:baz 3] -> #game.core.Baz{value: 3}


let's be precise with terminology: you have multiple record types, Foo, Bar, Baz?


or multiple record instances?

Noah Bogart16:08:07

Multiple record types (they all implement a protocol)


it sounds like each of those is a record type, with a key of value?


Then you want {:bar ->Bar :baz ->Baz ... ...} I think.


you don't need an atom for that, I'd just create the hash map after defining all the record types


given (def record-types {:bar ->Bar ... ...}) you can then use (defn construct-record [[k v]] ((record-types k) v))

Noah Bogart16:08:45

I'm at 37 different records, so I've found it's better to register them one at a time: (defn register-records [title constructor] (swap! records assoc title constructor)) and then (register-records :foo ->Foo) below each defrecord lol


in that case, just use alter-var-root, you still dont' need an atom


there's no code path that would be a race condition when loading the ns

Noah Bogart16:08:37

what do i gain for using alter-var-root instead of an atom?


you lose the complexity of atom


it's a domain tool that doesn't help in this domain


not only that, an experienced clojure user is likely to draw incorrect conclusions from seeing its usage (that the map might change as a consequence of app level code during runtime)


alter-var-root is a tool for building up a var in a namespace, that maps exactly to your task


atom is a tool for safe runtime concurrent change of container contents, none of that applies here

Noah Bogart16:08:37

it's also the cleanest "change the value of something" semantics


alter-var-root works exactly the same way swap! does, with less complexity


you don't need the extra protections atom offers


and using an atom adds a deref at usage


but I think we both understand the consequences of each usage, so use your judment, I'm just trying to convince you of a specific architecture at this point, haha

Noah Bogart16:08:38

no worries! i work alone on this stuff, so my experience of the wider clojure standards is lacking

Noah Bogart16:08:48

i'm only pushing back to make sure i fully understand the consequences. in my head, alter-var-root was naughty lol, and atom was the expected method of changing a value at runtime


(usage of atom where concurrent update is provably impossible is a pet peeve of mine, others might not find it as objectionable)

Noah Bogart16:08:04

ha everyone has their own peeves

Noah Bogart16:08:40

thanks for the feedback, i really appreciate it


there's this idea, "principle of least power", I guess that plus parsimony (don't add behaviors that increase complexity when their features are not needed), lead me to the opinion


vars are weak and simple, so better than atoms in two ways


(especially when the atom is going to be inside a var)

Noah Bogart16:08:17

(when are atoms not in vars?)


when you use them inside a local closure, eg. a function that launches multiple futures with an atom to combine efforts

Noah Bogart16:08:34

ooooo interesting


(defn parallel-foo [jobs] (let [results (atom [])] (run! deref (mapv (do-work results) jobs)) results))

Noah Bogart14:08:10

->Foo instead of just Foo, I mean


How does one determine which version is the latest stable one when adding libs to deps.edn?


You can use the ancient tool to check for any outdated dependencies if that's what you mean


Thanks, so without this, you check for maven central, find the version number and use that?

Michael W15:08:49

You can use "RELEASE" for the version number to get the latest version. I use that when I'm developing, then when I'm ready to go to production I convert to actual version numbers.


Ah great thanks

Jeff Evans15:08:26

What would be the type hint for a Java primitive array type? Ex: for char[], would it be ^chars ?

Alex Miller (Clojure team)15:08:21

yes, but that's not valid in every type hint location

Alex Miller (Clojure team)15:08:38

(depends whether the hint is evaluated or not)

Alex Miller (Clojure team)15:08:37

^chars will generally work in signatures and return types

Jeff Evans15:08:20

thanks, Alex, appreciated!

Jim Newton16:08:47

I'm looking at comprehensions. It is not mentioned in the documentation i'm reading wether the order of :let :when and :while is important. Of course one can experiment, but it would be great if that were mentioned in the doc. Are these the definitive docs, or must a short summary of something more extensive elsewhere?

Jim Newton16:08:19

Looks like the modifiers can be used multiple times and the order matters.

clojure-rte.core> (for [x [1 2 3]
                        :when (< y x)
                        :let [y (inc x)]
Syntax error compiling at (clojure-rte:localhost:61915(clj)*:7005:31).
Unable to resolve symbol: y in this context

clojure-rte.core> (for [x (range 10)
                        :let [y (inc x)]
                        :when (odd? y)
                        :let [y (inc y)]
(2 4 6 8 10)


right, I would expect them to be meaningful sequentially, and have previous clauses in scope, but not prior clauses

Jim Newton16:08:31

yes, that's different that the impression I had reading the documentation, which I'm happy about.

Cris B21:08:30

@jimka.issy You might want to have a look at Borgatti's . It's in prerelease, and has no official imprimatur, but contains a lot of detail on clojure.core functions. I haven't seen much mention of it in the Clojure community thus far. Perhaps it's 'essential' pretensions has rubbed people up the wrong way, or maybe experienced clojurists glean enough from reading the clojure.core source.


It's a pretty amazing piece of work @cb.lists but it is also pretty specialized: it's a very deep dive on everything in the core of Clojure, so it's not a book to learn Clojure from, but it's also a lot more than just using the source code as a reference. It has examples for everything, discussions on how certain features compare to other Lisps. It's an encyclopedia for Clojure.


It's also one of the few Clojure books I don't own and it's purely subjective: it's just not a style that I like -- I don't have anything similar for other languages either (even tho' such books exist for several of the languages I've worked with over the years).


Hey team, quick noob question: what is the idiomatic way to handle uncaught exceptions in ring apps? Am going to go ahead and create a little exception handler middleware (this seems to be the way on google) -- but if ya'll do something different let me know : ]

(defn wrap-exception [handler]
  (fn [request]
    (try (handler request)
         (catch Exception e
           (log/errorf e "uncaught exception, request=%s" request)
           {:status 500
            :body "Exception caught"}))))


that's what it's always looked like in my experience


(plus calls to services like honeybadger / pagerduty etc. as apropriate)


depending on codebase style you might want to catch AssertionError there too

❤️ 3
Cris B22:08:00

@seancorfield I haven't bought any of that class of book for other languages either, but I did this one (as you say, not to learn from, but for quick dips into specific areas of puzzlement or interest). I think why it seemed useful for Clojure specifically was in part because the official documentation is generally so terse. Also this kind of reference for many languages/platforms gets outdated quickly - Clojure's stability makes that less of a problem.


@cb.lists What is incredible is that Renzo has come up with examples and narrative around some virtually unused core functions -- take a look at the section on ref and ensure etc, for example!

Cris B22:08:58

Indeed - large swathes of the book are also a tad over my (clojure) head right now. I'm hoping to grow into it.