This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-08-10
Channels
- # announcements (1)
- # babashka (18)
- # beginners (122)
- # calva (18)
- # cider (3)
- # cljs-dev (2)
- # cljsrn (3)
- # clojure (102)
- # clojure-europe (15)
- # clojure-france (2)
- # clojure-nl (1)
- # clojure-portugal (1)
- # clojure-spec (3)
- # clojure-uk (8)
- # clojurescript (46)
- # clojureverse-ops (5)
- # code-reviews (1)
- # conjure (2)
- # cursive (15)
- # datalog (13)
- # datomic (18)
- # emacs (4)
- # fulcro (8)
- # helix (8)
- # instaparse (1)
- # introduce-yourself (2)
- # jobs (4)
- # leiningen (23)
- # lsp (26)
- # malli (21)
- # off-topic (34)
- # pedestal (21)
- # polylith (6)
- # reitit (5)
- # remote-jobs (3)
- # schema (1)
- # sci (8)
- # shadow-cljs (8)
- # spacemacs (3)
- # sql (30)
- # testing (31)
- # tools-deps (21)
- # vim (25)
- # xtdb (8)
Hello! Is this channel a good spot to ask specific code refactor questions? I have a function to do some data transformation that works, but feels a bit ugly. i would be keen to share the code with clojurists and see if there's any recommended changes to make it more idiomatic; but not sure if this is the right spot to share/request something like that!
It is something where a refactor of tangible code would likely cause some lighbulbs to go off in my head on how to write clojure.
Thank you, @deleted-user! Here is a https://gist.github.com/zachmandeville/3f4f805b5bc6dae3bd67c9899f48a832 for what I am trying to do.
@webmaster601 :labels
would be (distinct (map :month input-data))
yes?
And perhaps most of the rest could be gotten by (group-by first (map (juxt :store :total) input-data))
user=> (group-by first (map (juxt :store :total) input-data))
{"Mitesh" [["Mitesh" 5.9] ["Mitesh" 5.5]], "Moore Wilson" [["Moore Wilson" 85.16] ["Moore Wilson" 33.45]], "New World" [["New World" 75.26] ["New World" 84.28]], "Pak n Save" [["Pak n Save" 26] ["Pak n Save" 200.36]]}
user=> (reduce-kv (fn [acc k v] (conj acc {:label k :data (map second v)})) [] *1)
[{:label "Mitesh", :data (5.9 5.5)} {:label "Moore Wilson", :data (85.16 33.45)} {:label "New World", :data (75.26 84.28)} {:label "Pak n Save", :data (26 200.36)}]
(that reduce could add the :backgroundColor
as well, by doing a lookup on store-color
of the k
value)
So here's the whole thing in the REPL:
user=> (def store-color {"Moore Wilson" "#88A096"
#_=> "Pak n Save" "#BBAB8B"
#_=> "Mitesh" "#8A4F7D"
#_=> "New World" "#EF8275"})
#'user/store-color
user=> (def desired-data {:datasets (->> (group-by first (map (juxt :store :total) input-data))
#_=> (reduce-kv (fn [acc k v] (conj acc {:label k :data (map second v) :backgroundColor (store-color k)})) []))
#_=> :labels (distinct (map :month input-data))})
#'user/desired-data
user=> desired-data
{:datasets [{:label "Mitesh", :data (5.9 5.5), :backgroundColor "#8A4F7D"} {:label "Moore Wilson", :data (85.16 33.45), :backgroundColor "#88A096"} {:label "New World", :data (75.26 84.28), :backgroundColor "#EF8275"} {:label "Pak n Save", :data (26 200.36), :backgroundColor "#BBAB8B"}], :labels ("06/21" "07/21")}
Sometimes simplifying the data before processing can lead to a much simpler transformation.
(disclaimer: I've been doing Clojure in production for just over a decade at this point -- it does take time to internalize the core functions because that's a very broad space!)
Ah, the use of the threading macro is nice. I like how your implementation clearly shows the intended shape of the data.
which is easier to understand from a general shape. I think I had some internalized feeling that I should only use the input-data
value once.
and I like how you transform it separately for labels
and datasets
, so the initial shape for each is easier to work with. Thank you for this!!!
It's true that it makes two passes over the input data. That may or may not be a performance consideration "in the wild". Making a single pass adds complexity so it's a trade off about which is faster overall.
why can't I use get
on a list? if it always returns nil, wouldn't it be better if it threw an exception?
for lists, you should use nth
its more around the thought process of https://lispcast.com/nil-punning/
> why can't I use `get` on a list?
@U01UDS4A1K8 This can be annoying, yes. However get
is intended for hash-maps (i.e. associative data structures). Ref: the function signature in (clojure.repl/doc get)
.
It only accidentally works for Vectors, because they are also associative data structures under the hood, just like hash-maps, but keyed by index position instead of arbitrary keys.
Clojure lists are not associative underneath.
Comparing the interfaces these structures implement my prove to be illuminating. Look for clojure.lang.Associative
:
user=> (supers (type '()))
user=> (supers (type []))
user=> (supers (type {}))
user=> (supers (type #{}))
Just to be sure: Both nth
and get
have a parameter where you can pass in a default value, which it will return in case it doesn’t find the thing.
And you can kind of think of nth
as an iterative next
, so it will make sense why it would return nil
True, but (get '(any list ...) any-index 42)
will always return 42. i.e. the default return, in case of a list, doesn't alter OP's original conundrum. Instead of nil, they'll just get back 42 each time.
Yeah, I wanted to add something to:
> yes, I’m just confused why it’s like this (always returning nil instead of throwing)
Because I just recently also had trouble to understand nil
and how different core functions work with it and what it means etc.
> get is intended for associative data structures > [...] > lists are not associative why no exception (type error) then?
No it’s not! It has very much to do with how Lisps in general treat nil
and polymorphism.
> why no exception (type error) then?
That's a very good question, and AFAIK, the answer to that is an age-old debate.
I personally prefer to "define errors out of existence", as John Ousterhout puts it in this lecture: https://www.youtube.com/watch?v=bmSAYlu0NcY&t=1315s
If the semantics of get
is to get a thing at a position or <default value> or nothing, and if it's already possible to use nil
or some custom sentinel value like ::error/not-found
for semantic error handling, do we need to force an Exception?
I don't know the right answer. However it stands out that get
and nth
are not symmetric in this regard.
user=> (get 1 1)
nil
user=> (get "foo" 1)
\o
user=> (nth 1 1)
Execution error (UnsupportedOperationException) at user/eval10616 (REPL:1).
nth not supported on this type: Long
user=> (nth "foo" 1)
\o
ah, so get
will always succeed no matter the type? that's better - I thought it was just lists that make it return nil
> why no exception (type error) then?
My comment on this is that Clojure rarely wants to throw exceptions. It concedes to them when doing interop (which in turn means that (nil)
will cause a NullPointerException
) but in general, sticking to clojure.core
should not lead to an exception anywhere.
If not for that, then I would think that it would be appropriate to have some kind of exception. However, it can’t be a ClassCastException
, since what actually happens in get
is that it looks for the ILookup
interface, but if that’s not present then it does a couple of fallbacks, such as doing nth
on arrays and strings. Only when all other options fail does it revert to nil
(or the not-found
value).
Regardless as to whether it’s a good idea, it could potentially break existing code to change it, so it will never get changed.
maybe kind of a random idea but how would you assess this? Is this a reasonable way to dispatch or completely bonkers?
(defmulti view #(s/conform ::component %)
Edit: I realized myself how this isn’t particularly useful 😄Hey — does someone have a Gist (or something) for an emacs configuration setup that leverages cider (along with the lighttable like interim values, etc.)
I'm too lazy to configure emacs so I use this https://github.com/hlissner/doom-emacs , it setups vim keybindings
Thanks — I ended up going the Spacemacs route and am modifying: https://practical.li/spacemacs/. I prefer the normal Emacs key-bindings, so I’ve had to remove the VIM options there which are enabled by default.
I've been adding in the command names into the Practicalli Spacemacs book, for those not using Evil (or Spacemacs). If there is anything not clear or missing, let me know.
Thanks, @U05254DQM! I was impressed with the setup you were using a few weeks ago during the SciCloj session
Is there a function in standard library that does something like this?
(the-fn 2 [1 2 3 4]) ;-> [[1 2][2 3][3 4]]
(the-fn 3 [1 2 3 4]) ;-> [[1 2 3][2 3 4]]
(partition 2 1 [1 2 3 4]) ;-> ((1 2) (2 3) (3 4))
(partition 3 1 [1 2 3 4]) ;-> ((1 2 3) (2 3 4))
A question about structural editing with parinfer.. not sure if it's more appropriate to ask in #vim or if this is more general. Say I have a form like:
(t/is (s/valid? (s/coll-of int? :kind set? :count 3)
(q/get-ints crux-node)))
Now I want to :refer [is]
and refactor t/is
to just is
. When I delete the t/
portion, my code gets formatted as such:
(is (s/valid? (s/coll-of int? :kind set? :count 3
(q/get-ints crux-node))))
which obviously isn't right, and I have to go to the second line and manually mess around with the indent level to put at the same level as the first argument to s/valid?
.
Is there some better way of doing this? Is my editor just misconfigured or is my editing workflow wrong?looks like a bug in vim indenting or parinfer to me
likely parinfer because I have never seen indenting misbehave like that in vim
are you sure the formatter ran again? If you just did a textual replacement that's what i would expect it to look like
good point, try running =-
which re-indents the entire top level form
That resulted in:
(is (s/valid? (s/coll-of int? :kind set? :count 3
------------->(q/get-ints crux-node))))
depending on your settings i'd expect (q/get-ints crux-node)
to be underneath int?
. odd that it is there
Sorry, bad edit on my part. Yes, it's:
(is (s/valid? (s/coll-of int? :kind set? :count 3
-------->(q/get-ints crux-node))))
but i suspect you want to spit the (q/get-ints crux-node)
out of the schema definition
that's a correct indent unless you have some special rule for s/coll-of
yeah. you want (s/valid (s/coll-of ...) (q/get-ints crux-node))
you have the item you are validating inside of your schema definition. which is why the indentation looks off. the indentation immediately gives it away
it will align like so and be obviously correct when you correct your function calls
(s/valid? (s/schema)
(the-object))
this is why I hate parinfer - it turns sloppy indentation into bugs caused by wrongly placed parens
Right, so is there a way around that somehow? Basically, when deleting the t/
portion of t/is
, I want the arguments of that function call to stay in place.
well, parinfer uses indentation to dictate semantics. when you want to really use semantics to dictate indentation
to be less glib, try using =-
to reindent after the textual substitution, and not changing indentation by deleting / inserting spaces ever
I've seen Calva in a video before - looked nice, but it's not available as a vim plugin.
I use the vscode neovim plugin together with calva. Kind of works nicely.
As for my workflow dealing with your issue - what I do there is to delete the t/
(i dont let parinfer autoformat), press tab
which realigns the indent and then format with shift-tab
because parinfer interprets respacing as meaning "I want to change nesting"
"based on indentation, assume the parens (semantics) of code" vs "based on parens, indent where it should go". yeah. i didn't mean to be glib, just pointing out it seems backwards to me
I like vim-sexp
others like paredit.vim
they don't try to claim the same keypresses
vim-sexp gives you text objects for manipulating forms, it can be used with parinfer
it's not an indentation tool, it's an editing tool (which understands clojure code structure)
I think I installed vim-sexp because it's required for vim-iced, but didn't really look into it too much.
I use parinfer-rust and vim-sexp (and vim-sexp-mappings-for-normal-people) and I am getting your desired behavior when deleting the t\
. I wonder why we are getting different results?
I installed parinfer-rust and now everything seems to be working as desired. Thanks!
maybe parinfer-rust does the form-reindent implicitly on edit?
Maybe. It's the only parinfer implementation I've been able to use. All the others just never really got the smart mode done correctly for me
It works if I dti
or if I just manually use x
. I'm wondering if it's because I copy and pasted in your expression directly. Let me type it out and use the returns and such and see if I get the same
Is there a way to dynamically define a new var? I want to dynamically define some vars but this did not work as I expected:
user> (map #(def % 42) ['foo 'bar])
(#'user/p1__26575# #'user/p1__26575#)
user> foo
Syntax error compiling at (*cider-repl clojure/foragr:localhost:35287(clj)*:1:7931).
Unable to resolve symbol: foo in this context
Context: This is for use in a map for *data-readers*
which requires function vars as the values - I'd rather not define the functions by hand as they are very similar and there are several so I'm trying to define them dynamically and then I need to make a var for each.
for that use case, I'd make a higher order function that returns the reader function, and use def to make the var, in order to use def programmatically you need a macro
it's "macro contagion" - since def is a macro, you can't make a form that extends its functionality without making another macro
I tend to be highly suspicious of macros that wrap def, for example when debugging it's frustrating to see a function misbehave and not be able to find the definition
@alex.sheluchin Yeah mine works correctly. Maybe try throwing set smartindent
in your init.vim (which btw, I am using nvim and not vim so maybe there is different behavior there?)
I'm on nvim too. Are you using =-
to format after the xx
? It doesn't reformat anything on its own for me after I delete the two chars, it only does so after the =-
.
try with that smartindent option. That is the only thing I can think that might be different for my config. xx
just works for me
if you are still having troubles there are some vim gurus over in #vim that could probably figure it out
is it typical to work with maps instead of objects when it comes to error handling/ exception-based control flow? if so, is there a typical way to classify or type those maps? eg {:error/type :banana-error}
? i'm thinking about other languages where you'd write a error class and catch that downstream based on the type
part of what prompts my question is the pedestal error handling
which does have a convenience function that dispatches based on class
which suggests to me they are not accustomed to using maps instead of objects
or maybe i'm misreading that error-dispatch
function. maybe it is dispatching based on an unqualified key :exception-type
in a plain map
that page is all about using maps, as passed along via ex-info, so seems like it is largely about maps as I read it
when interop'ing with Java libs, you do need some strategy to also handle Java typed exceptions though
the slingshot library takes ex-info and the map orientation a lot further (ex-info actually started there and was adopted by core)
the https://github.com/cognitect-labs/anomalies lib may be a useful frame for categorizing errors
thank you
this makes sense to me.
i'll look at the anomalies lib, too.
We use anomalies at work. It's a good set of basic error types.