This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-09-11
Channels
- # announcements (3)
- # babashka (6)
- # babashka-sci-dev (37)
- # beginners (39)
- # calva (1)
- # clj-kondo (55)
- # clj-on-windows (1)
- # cljdoc (1)
- # clojure (30)
- # clojure-dev (3)
- # clojure-europe (8)
- # clojure-losangeles (1)
- # clojure-morsels (1)
- # clojurescript (26)
- # conjure (8)
- # graalvm (5)
- # helix (6)
- # hyperfiddle (12)
- # meander (6)
- # minecraft (1)
- # pathom (17)
- # polylith (1)
- # releases (2)
- # shadow-cljs (2)
- # sql (1)
- # squint (4)
Hello! I want to write clojure.specs for namespaced keywords that are bound to variables, like nskw
below. Here is what I tried:
(ns asr)
(require '[clojure.spec.alpha :as s])
(defn sdef [nskw pred]
(println (= nskw ::tunit))
(s/def nskw pred))
(sdef ::tunit list?)
Here is what I get:
> true
> asr/nskw
Here is what I want:
> true
> asr/tunit
As an aside, the reason I want this is that I am translating another specification language (ASDL — ancient :)) into clojure.spec. I want to translate the ASDL automatically because the ASDL specs are evolving and I don’t want to maintain the parallel clojure.specs manually. More asides: I want clojure.spec for test-generation. I want to generate large volumes of test cases that conform to the clojure.specs (and to the ASDL) for beating the hell out of a compiler backend. The specs are for the compiler intermediate representation, a tree-like Abstract Semantics Representation (asr). The compiler is lpython (https://lpython.org)
Any hints for me?s/def
is a macro, which turns symbol keys (the first arg) into https://github.com/clojure/spec.alpha/blob/13bf36628eb02904155d0bf0d140f591783c51af/src/main/clojure/clojure/spec/alpha.clj#L355. That means you'll need a macro for what you want to do.
@UJY23QLS1 great to read the source. for the pointer.
@UHJH8MG6S eval is probably the easiest solution in the short-run, and for my collaborators (who are not clojurians) to read. macrology is fun, but I expect they won't go to the effort of understanding or maintaining macros.
I have a namespace with 5 functions that all have same first two parameters, both being a kind of an options map. Usually they are called one after another, so it seems silly to always specify two options maps, especially when if user were to specify a different options map in each call it would be a bug. So I wonder how to simplify this so I wouldn’t have to repeatedly provide these options maps. Now in OOP this would be quite simple, create a class that takes 2 maps in constructor and then add 5 functions as methods without the 2 params. In Clojure I can make a Protocol, then a Record and have that extend the protocol, but that seems quite an overkill for this. But another way would be to have a function that is a “constructor” and it returns a map of functions that close over the two maps. But I never see this pattern in Clojure code. What other options are there?
If the calling pattern is always exactly or almost exactly the same, I would simply extract that calling pattern.
So instead of a user having to call (f m1 m2)
and (g m1 m2)
right after, they would call (x m1 m2)
where (defn x [m1 m2] (g m1 m2) (f m1 m2))
.
If the calling patterns are different between use cases, I'd still probably tend to use maps explicitly. But if you really don't want to do that, dynamic bindings is another viable option. And, of course, what you have described are still viable options themselves - what to choose depends on the exact use cases. Just note that you won't be able to completely prevent a user from calling g
with different parameters than those that were passed to f
- and not that you should, so that's a good thing.
hm… yeah I don’t see big savings there
the calling patterns are different enough that I cannot premake combinations
You could have the caller bundle the two maps into one, then use the threading macro (-> twomaps (f 42) (g) ...)
Hm I’ll think about it, thanks
Sounds to me like a job for partial
.
(defn foo [a b] ... )
(defn bar [a b c] ... )
(let [foo-mn (partial foo m n)
bar-mn (partial bar m n)]
(foom-mn)
(bar-mn z))
you could have a “constructor” function that takes options maps and returns a reify
d instanced that provides a protocol of the functions you care about
Only have one options map. Either merge them, or just have an options map of options map:
(-> {:x-options x-options
:y-options y-options}
(fn1)
(fn2 extra-arg)
(fn3))
Or
(-> (merge x-options y-options}
(fn1)
(fn2 extra-arg)
(fn3))
And you can have a constructor for that map so clients don't have to know how the functions want it:
(defn make-xy-options
[x-options y-options]
;; Either merge or combine
(merge x-options y-options))
;; Or combine
;; {:x-options x-options
;; :y-options y-options})
So all together it looks like:
(defn make-xy-options
[x-options y-options]
{:x-options x-options
:y-options y-options})
(defn fn1
[xy-options]
...)
(defn fn2
[xy-options extra-arg]
...)
(defn fn3
[xy-options]
...)
(-> (make-xy-options x-options y-options)
(fn1)
(fn2 extra-arg)
(fn3))
Keep in mind in order to allow threading they all have to return the xy-options. But they don't have too, just if they return something else you can't thread them. If they do side-effects only you could still thread them with doto
instead. But otherwise you would call them without threading:
(let [xy-options
(make-xy-options x-options
y-options)
fn1-result (fn1 xy-options)
fn2-result (fn2 xy-options
extra-arg)
fn3-result (fn3 xy-options)]
(+ fn1-result
fn2-result
fn3-result))
This is a common idiom by the way. Everytime you have a set of functions all operating over the same data using the above pattern is common. Like @U0HG4EHMH said, it's OOP without the Os, which some people call it Data Oriented Programming DOP. Because we've replaced the Object with a Data-structure instead.
I’ve seem this pattern before
what do you mean by "clearing" in "locals clearing?"
By locals clearing he means that local variables are set to nil whenever it is first possible to so so instead of being referenced for the whole scope
This is done to enable garbage collection
@UDRJMEFSN there is a clojure.compiler.disable-locals-clearing
flag but it works for the whole execution, the only other way is to artificially keeping the local referenced, but of course this is not "disabling" clearing
I’m trying to understand tools.deps’ Tools abstraction. IIUC the idea is that tools that don’t use the classpath directly can be invokved and installed separate from any project. I would therefore expect that they’re the intended deployment mechanism for linters (e.g. clj-kondo, zprint), etc. However, because it works analogously to -X
, it’s intended for tools that don’t write a main-style entrypoint. Is that mostly accurate?
(I ask because zprint /feels/ like I should be invoking it -Tzprint.)
More and more tools are adding exec entry points, often in addition to -main
entry points, so they can be invoked via -T
/`-X`. Since it's the newer way to do stuff, not all tools have "caught up" yet.
thanks! any advice for how to use these in a company context? I imagine a babashka task that ensures all the tools are installed or something
In company context you probably shouldn't use globally installed tools, instead declare aliases in the project directly for them, you can have project local tools that you run with -T:tool
that similarly doesn't use the classpath, but don't need a global install.
This makes sure everyone who pulls down the project gets the exact same set of tools and the same versions of them,
You could also wrap zprint and company inside your tools.build and call them with -T:build zprint if you wanted as well.
@U07QKGF9P Yeah, as far as use within a company context, I agree with @U0K064KQV -- put the tools/aliases/etc in your company project's repo / deps.edn
/ build.clj
and use them that way, even if that means invoking them via their -main
using something like https://clojure.github.io/tools.build/clojure.tools.build.api.html#var-process