This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-12-20
Channels
- # adventofcode (29)
- # announcements (7)
- # aws (1)
- # babashka (3)
- # beginners (43)
- # biff (20)
- # clj-kondo (44)
- # cljs-dev (20)
- # clojure (74)
- # clojure-europe (24)
- # clojure-finland (2)
- # clojure-nl (13)
- # clojure-norway (3)
- # clojurescript (31)
- # code-reviews (1)
- # community-development (12)
- # cursive (3)
- # datomic (6)
- # emacs (1)
- # fulcro (25)
- # interop (7)
- # introduce-yourself (2)
- # leiningen (30)
- # nbb (3)
- # overtone (1)
- # podcasts-discuss (5)
- # polylith (24)
- # practicalli (1)
- # reclojure (1)
- # reitit (13)
- # rum (7)
- # shadow-cljs (12)
- # sql (23)
- # squint (51)
- # test-check (1)
- # testing (2)
- # tools-deps (2)
I guess defmulti
is stateful, since the value of (methods %)
changes as you add & remove methods.
Is there a way to subscribe to that state?
It uses internal state of a https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/MultiFn.java#L58 to store methods, so I guess no, you can't subscribe to it. But you could poll the method table to see if it changed
(.-method-table %)
returns an atom, so it seems like calling add-watch
on that would work.
It does, but in ClojureScript, not in Clojure
What was the design decision when adding update-vals
?
I'm used to use
(->> []
(map identity)
(reduce conj #{}))
But update-vals
expects the function to be in the end of its arg list and that always throws me off.
Why this design choice? I know that we have as->
function but I don't like to use it.
Does it mean that syntax of map
and reduce
is bad? Should their signatures also have the function in the last place? I don't understand this inconsistency for update-vals
. Even in my own function signatures I like to send the actual data in the last place.Functions that work on maps take the map as their first argument, while functions that work on seqs take the seq as their last argument, for use with -> and ->> respectively.
Ah, alright. I see. assoc
But well.. here we also see the input map as a sequential update problem, like map
.
i.e. you don't work on one element but on all of values.
I don’t think it is about the kind of work, but about the structure of the chained data. You can chain operations on sequential data together with ->>
and on associative data using ->
.
@U4P4NREBY this is what I want to do:
(->> [1 2 3 4 5]
(map (juxt identity identity))
(into {})
(update-vals inc)) ;; <- error
; (err) Don't know how to create ISeq from: clojure.core$inc
The first part is completely arbitrary. I only want to show that update-vals
is almost the same thing as map
. It's an example.
it's not, tho, since update-vals works on associative structures, and map works on sequential structures
I tend to write this code more frequently than I'd want to write it:
(defn map-vals [f m]
(->> m
(map (fn [[k v]] [k (f v)]))
(into {})))
(map-vals inc {:a 1 :b 2})
(associative? (map (fn [x y] (clojure.lang.MapEntry. x y)) (range 8) (range 8)))
=> false
(update-vals (map (fn [x y] (clojure.lang.MapEntry. x y)) (range 8) (range 8)) inc)
=> {0 1, 1 2, 2 3, 3 4, 4 5, 5 6, 6 7, 7 8}
(associative? (seq {:x 5}))
=> false
(update-vals (seq {:x 5}) inc)
=> {:x 6}
I don't understand what you mean. If I want to input a map and get a map as an output I do it. There is no guard against that.
Of course map
is not associative, because it produces a lazy-seq. But I want a {}
as an output, not a list of tuples that I'd then put into a new map anyway. This was what my own map-vals
function does. But I don't want to rewrite it again and again.
Maybe I miss something.
Or maybe I should overoptimize everyting to prevent {} -> {}
transformations and only do it once.
or you could take into consideration that ->
is a better tool for working with maps than ->>
and restructure your code to work with the grain, instead of against it
Does not resolve your philosophical dispute with the design, but maybe something like this will help you?
(defn flip [f] (fn [y x] (f x y)))
(->> [1 2 3 4 5]
(map (juxt identity identity))
(into {})
((flip update-vals) inc))
; => {1 2, 2 3, 3 4, 4 5, 5 6}
https://clojure.org/guides/faq#seqs_vs_colls and https://clojure.org/guides/faq#arg_order cover parts of this
> Note that this is not the same as taking the primary operand last.
This was the piece of philosophy that I was thinking about.
I wanted the map to come last. But well.. oh well. For me update-vals
operates on a sequence of items in a map, not on a map.
This is... gray area for me. But alright, the top-level data structure is a map so what can I do.
update-vals is map -> map so it's a collection function
So why filter
is not a collection function?
it's seqable -> seqable
so it's a seq function
seq functions often seqables, coerce them to seqs, operate on them as seqs, and return seqs (or occasionally seqables)
examples: map, mapcat, keep, filter, remove, take, drop
This is what I wrote in my larger comment because I read about it in clojure guide that you sent.
seqable -> seqable => last
map -> map => first ;; also other data structures probably as well
My original comment was about this updating because when I update parts of my data structure then I'm used to provide the data structure last as if it was a collection.
Example of it would be clojure.walk/postwalk
which takes fn
and any form.
So what I was thinking about was "if I update one value in a map then it's first operand" and "if I update more than in some kind of sequential way then it's last"
But it doesn't matter as basically only data type matters.
One thing about ->
and ->>
is that it's easier to switch into ->>
from ->
than the reverse:
(-> {:y 3}
(assoc :x 7)
(update-vals (partial * 8))
(->>
(mapcat reverse)
(partition 2)
(map vec)
(into {}))
(assoc :z 9)
(conj [9 8]))
switching to ->
from ->>
is cumbersome:
(->> (range 8)
(partition 2)
(map vec)
(into {})
->
(->>
(->
(assoc :y 7)
(update-vals (partial * 10))))
(map reverse)
(map vec)
(into {}))
unless anyone knows a better way (using only ->
and ->>
) ? Not that it's ever really needed but it's an interesting exerciseI haven't been running into a problem like this because you work on concrete data structures when you use ->
and then you move to lists when you use ->>
. So you're rewriting your code in both cases but yes, it's harder to produce concrete data structures from lists.
Also your code is too long. I think try to not chain more than ~5 of these ->>
operations in one code chunk.
Also doubly nested ->>
and ->
... why didn't you use as->
?
(it was only an example @U028ART884X! Contrived for discussion purposes, and yes I know about as->
that's why I specified ->
and ->>
only)
I am trying to create a GCP function using Java 11.
Cloud Function type: pub-sub. Uploaded a standalone jar generated using lein uberjar
I am getting below error while deploying my function. Does anyone got any idea ?
Exception in thread "main" java.lang.ExceptionInInitializerError at clojure.lang.Namespace.<init>(Namespace.java:34) at clojure.lang.Namespace.findOrCreate(Namespace.java:176) at clojure.lang.Var.internPrivate(Var.java:156) at com.hello.GCPClojureHandler.<clinit>(Unknown Source) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490) at com.google.cloud.functions.invoker.BackgroundFunctionExecutor.forClass(BackgroundFunctionExecutor.java:122) at com.google.cloud.functions.invoker.BackgroundFunctionExecutor.forClass(BackgroundFunctionExecutor.java:115) at com.google.cloud.functions.invoker.runner.Invoker.startServer(Invoker.java:247) at com.google.cloud.functions.invoker.runner.Invoker.main(Invoker.java:121) Caused by: java.io.FileNotFoundException: Could not locate clojure/core__init.class, clojure/core.clj or clojure/core.cljc on classpath. at clojure.lang.RT.load(RT.java:462) at clojure.lang.RT.load(RT.java:424) at clojure.lang.RT.<clinit>(RT.java:338) ... 12 more
I don’t know the answer, but it is worth to see if you can use Cloud Run
instead of functions
Thank you @U0WL6FA77. Will explore that as well. 🙂
That error looks a bit like clojure core isn't included .. if you look in the jar you've generated, can you find the clojure/core__init.class
file?
I'm not sure I remember what lein
does if you don't have a version of clojure as a dependency. Maybe it works fine if you lein repl
or whatever, but doesn't include it as a dep when lein uberjar
ing??
init class in present ➜ gcp-test jar tf ./target/gcp-test-0.1.0-SNAPSHOT-standalone.jar | grep clojure/core__init.class clojure/core__init.class
https://sparkofreason.github.io/jvm-clojure-google-cloud-function/ this article mentions that it may be due to class loading. But I am not sure what I need to do exactly.
yes
(gen-class :name "com.hello.GCPClojureHandler"
:implements [com.google.cloud.functions.BackgroundFunction]
:import [com.google.cloud.functions.Context])
and in project.clj aot is set
:profiles {:uberjar {:aot :all}}
and if you run that jar on your machine with something like
java -cp path/to/uber.jar com.hello.GCPClojureHandler
does it work? (I guess you may need to add a -main
function or something?)yes Error: Main method not found in class com.hello.GCPClojureHandler, please define the main method as: public static void main(String[] args) or a JavaFX application class must extend javafx.application.Application
I have to admit to having done way more aws lambda than gcp cloud functions ... but I don't remember having to do anything more than correctly implementing the interface ...
maybe the best thing to do is to follow the advice from that article and implement a small java shim?
Yeah, I am going to try that. Thank you so much 🙂
or maybe define clojure.main
as the entry point? if you can pass an arg to it to tell it which ns to load?
I just spent a bit of time tracking down what turned out to be a syntax error in my deps.edn file, which manifested as:
"Execution error (ClassCastException) at clojure.tools.build.tasks.write-pom/to-dep$fn (write_pom.clj:44).\nclass clojure.lang.PersistentVector cannot be cast to class clojure.lang.Named (clojure.lang.PersistentVector and clojure.lang.Named are in unnamed module of loader 'app')\n",
It surprised me that there wasn't some kind of spec violation flagged instead.
The build code does in fact check the basis against specs in various
places, but here, as far as I can tell, is the entire contents of the
specification:
(ns clojure.tools.build.api.specs
(:require [clojure.spec.alpha :as s]))
(s/def ::lib qualified-ident?)
(s/def ::path string?)
(s/def ::paths (s/coll-of string?))
That's it?
Is there some other place where well-formed deps.edn specifications
are nailed down in more detail?that's tools.build specs, not deps.edn
Ah. Am I correct in the understanding that the thing that needs to be well-formed is the basis, derived from the various layers of deps.edn files?
https://github.com/clojure/tools.deps/blob/master/src/main/clojure/clojure/tools/deps/specs.clj are specs for deps.edn
Ah! That's what I was looking for! Thanks!
if you have an example of what your error was happy to consider improving spec or error if it makes sense (but maybe move this to #C6QH853H8 or #C02B5GHQWP4)
In retrospect it was a dumb error.
{:deps {<library> {:mvn/version ... :exclusions [[nested-too-deep]]}, ...}...}
the Clojure CLI does this already but perhaps it is not being checked in this case via tools.build
I'll look into it
I think clj-kondo does validate deps.edn files too, or it can, so you could ask that in #CHY97NXE2
I'll do that. Thanks.
logged as https://clojure.atlassian.net/browse/TDEPS-238 and I'll look at it later
@UB3R8UYA1 if you have clojure-lsp setup, that can warn you when you have a deps.edn open in real time:
Does somebody already publish a tool or an alias that validates your deps.edn file against deps/specs.clj?
as I said above, the Clojure CLI should be doing this, but perhaps it is not
This would be the proper command, right?
clojure -M:kondo --lint deps.edn
This flags syntax errors, but the actual spec violation discussed in the thread above goes unnoticed.Ya, if you have a :kondo
alias setup in your deps.edn
, but folks typically use the clj-kondo
binary. So: clj-kondo --lint deps.edn
And lotsa people enjoy editor integration, so you can get lint warnings/errors as you type. If you are using clojure-lsp, it includes clj-kondo linting.
Yeah, personally I hate that. It's like having your mother look over your shoulder while you're trying to think.
I didn't realize the binary was so different from the aliased version.
Yes, there is no one right answer, and different opinions and ways of working are totally fine.
BTW, the binary and aliased version should be equivalent. The jvm launched kondo will just take a little longer to startup.
Just as a follow-up, I wound up adding this function to my copy of @U04V70XH6’s build.clj file:
(:require
...
[clojure.spec.alpha :as spec]
[clojure.tools.deps.specs :as deps-specs]
))
(defn validate-deps
"Throws an `ex-info` of type `::invalid-deps`, or returns `opts` unchanged"
[opts]
(println "Validating deps.edn...")
(let [deps (-> "deps.edn" (slurp) (read-string))
]
(when (not (spec/valid? ::deps-specs/deps-map deps))
(throw (ex-info "Invalid deps.edn"
{:type ::invalid-deps.edn
::spec/problems (-> (spec/explain-data ::deps-specs/deps-map deps)
::spec/problems
)
})))
(println "deps.edn conforms to clojure.tools.deps.specs")
opts))
Or perhaps just (spec/assert ::deps-specs/deps-map deps)
instead of that whole when
expression? And it will also throw if your deps.edn
is not valid EDN (I'm hoping read-string
is referred from clojure.edn
and isn't the core read-string
function?).
But I'm in the "editor integration" crowd with Calva/LSP/Kondo checking my deps.edn
files as I edit them -- we have 175 of them in our monorepo!