Fork me on GitHub
#beginners
<
2021-08-10
>
zach03:08:29

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!

zach03:08:06

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.

Cora (she/her)03:08:54

definitely, share it in a gist or something and anyone available who is up to it can take a look 😊

🙏 3
zach04:08:11

The function in the gist works, but feels fragile and verbose.

Cora (she/her)04:08:18

well, I mean, I'm off to bed but this is definitely acceptable here

zach04:08:32

haha, of course!

Cora (she/her)04:08:56

when I see something deeply nested like that my first instinct is to break those out into functions, even private functions, with good names

Cora (she/her)04:08:30

I'm not looking closely enough to see exactly how helpful that is here but that's a heuristic I tend to use

👍 3
seancorfield04:08:59

@webmaster601 :labels would be (distinct (map :month input-data)) yes?

seancorfield04:08:12

And perhaps most of the rest could be gotten by (group-by first (map (juxt :store :total) input-data))

zach04:08:14

Ah, I am discovering distinct and juxt! This is muuuch more succint and readable.

seancorfield04:08:59

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)}]

seancorfield04:08:05

(that reduce could add the :backgroundColor as well, by doing a lookup on store-color of the k value)

seancorfield04:08:03

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")}

seancorfield04:08:13

Sometimes simplifying the data before processing can lead to a much simpler transformation.

zach04:08:36

amazing. I'm going to recreate it now to work through/comprehend it.

seancorfield04:08:13

(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!)

zach04:08:08

Ah, the use of the threading macro is nice. I like how your implementation clearly shows the intended shape of the data.

zach04:08:03

e.g., it is basically (define data {:dataset (fn) :labels (fn)}).

zach04:08:05

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.

zach04:08:42

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!!!

seancorfield04:08:39

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.

3
sarna07:08:44

why can't I use get on a list? if it always returns nil, wouldn't it be better if it threw an exception?

Juλian (he/him)07:08:00

for lists, you should use nth

sarna07:08:42

yes, I'm just confused why it's like this (always returning nil instead of throwing)

sarna07:08:53

won't that hide silly programmer errors?

sarna07:08:03

and make them harder to debug

lispyclouds07:08:08

its more around the thought process of https://lispcast.com/nil-punning/

sarna08:08:22

thanks, I'll check it out :)

adityaathalye12:08:33

> 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 #{}))

dgb2312:08:56

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.

dgb2312:08:57

And you can kind of think of nth as an iterative next , so it will make sense why it would return nil

dgb2312:08:01

(nth nil 0)=> nil

dgb2312:08:38

(next nil)=> nil

adityaathalye12:08:50

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.

dgb2312:08:29

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.

sarna12:08:53

> get is intended for associative data structures > [...] > lists are not associative why no exception (type error) then?

sarna12:08:55

I think trying to make sense of it is overall detrimental haha

dgb2312:08:18

No it’s not! It has very much to do with how Lisps in general treat nil and polymorphism.

adityaathalye13:08:23

> 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&amp;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

☝️ 2
dgb2313:08:01

get and nth and friends flow nicely into if-let and when-let

2
dgb2313:08:14

And into higher order functions such as filter and some

sarna14:08:59

ah, so get will always succeed no matter the type? that's better - I thought it was just lists that make it return nil

noisesmith17:08:12

one of my favorite clojure absurdities:

user=> (= (get get get get) get)
true

😬 4
quoll20:08:35

> 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.

💯 4
☝️ 4
dgb2312:08:19

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 😄

Jon Boone17:08:55

Hey — does someone have a Gist (or something) for an emacs configuration setup that leverages cider (along with the lighttable like interim values, etc.)

paulocuneo01:08:25

I'm too lazy to configure emacs so I use this https://github.com/hlissner/doom-emacs , it setups vim keybindings

Jon Boone12:08:57

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.

👍 2
practicalli-john20:08:37

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.

👍 2
practicalli-john20:08:23

There is also a #spacemacs channel for specific Spacemacs related help

👍 2
Jon Boone14:08:35

Thanks, @U05254DQM! I was impressed with the setup you were using a few weeks ago during the SciCloj session

👍 3
raicotop17:08:06

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]]

emccue17:08:13

(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))

emccue17:08:13

(defn the-fn [n stuff]
  (partition n 1 stuff))

sheluchin18:08:21

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?

noisesmith19:08:05

looks like a bug in vim indenting or parinfer to me

noisesmith19:08:51

likely parinfer because I have never seen indenting misbehave like that in vim

dpsutton19:08:32

are you sure the formatter ran again? If you just did a textual replacement that's what i would expect it to look like

noisesmith19:08:23

good point, try running =- which re-indents the entire top level form

sheluchin19:08:18

That resulted in:

(is (s/valid? (s/coll-of int? :kind set? :count 3
                    ------------->(q/get-ints crux-node))))

dpsutton19:08:17

depending on your settings i'd expect (q/get-ints crux-node) to be underneath int?. odd that it is there

sheluchin19:08:45

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))))

dpsutton19:08:01

but i suspect you want to spit the (q/get-ints crux-node) out of the schema definition

noisesmith19:08:07

that's a correct indent unless you have some special rule for s/coll-of

dpsutton19:08:47

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

dpsutton19:08:28

it will align like so and be obviously correct when you correct your function calls

(s/valid? (s/schema)
          (the-object))

dpsutton19:08:49

vs the bad form you have

(s/valid? (s/schema arg
                    (the-object)))

noisesmith19:08:57

this is why I hate parinfer - it turns sloppy indentation into bugs caused by wrongly placed parens

dpsutton19:08:25

oh is there parinfer going on? yeah, it's inverted in my opinion

sheluchin19:08:29

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.

noisesmith19:08:35

not using parinfer

👍 2
dpsutton19:08:49

well, parinfer uses indentation to dictate semantics. when you want to really use semantics to dictate indentation

noisesmith19:08:36

to be less glib, try using =- to reindent after the textual substitution, and not changing indentation by deleting / inserting spaces ever

sheluchin19:08:49

I've seen Calva in a video before - looked nice, but it's not available as a vim plugin.

alpox19:08:03

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

alpox19:08:29

But yes you cannot use that all in vim

noisesmith19:08:55

because parinfer interprets respacing as meaning "I want to change nesting"

dpsutton19:08:13

"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

noisesmith19:08:14

I like vim-sexp

noisesmith19:08:40

others like paredit.vim

sheluchin19:08:47

lol it appears I have both parinfer and vim-sexp installed.

sheluchin19:08:18

Are those two surely going to conflict with each other?

noisesmith19:08:40

they don't try to claim the same keypresses

noisesmith19:08:03

vim-sexp gives you text objects for manipulating forms, it can be used with parinfer

noisesmith19:08:32

it's not an indentation tool, it's an editing tool (which understands clojure code structure)

sheluchin19:08:50

I think I installed vim-sexp because it's required for vim-iced, but didn't really look into it too much.

Chase19:08:24

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?

sheluchin17:08:15

I installed parinfer-rust and now everything seems to be working as desired. Thanks!

noisesmith19:08:05

maybe parinfer-rust does the form-reindent implicitly on edit?

Chase19:08:48

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

sheluchin19:08:57

How exactly are you deleting the t/? Maybe that makes some difference?

Chase19:08:03

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

Ryan Tate19:08:19

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.

noisesmith19:08:19

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

🙌 2
Ryan Tate19:08:44

Ah thanks!! perfect.... for some reason a macro did not occur to me

Ryan Tate19:08:03

i've got the higher order function written was just blocked on this, thanks muc

noisesmith19:08:35

it's "macro contagion" - since def is a macro, you can't make a form that extends its functionality without making another macro

noisesmith19:08:23

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

Ryan Tate20:08:57

fair point, it's like 5 defs i may just type them out

indy13:08:17

You can use intern

indy13:08:25

But yeah if it’s 5 defs and just an effort to not repeat code, then it’s not worth it

Chase19:08:46

@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?)

sheluchin19:08:51

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 =-.

Chase19:08:35

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

sheluchin19:08:53

I always have smartindent on.

Chase19:08:08

if you are still having troubles there are some vim gurus over in #vim that could probably figure it out

sheluchin19:08:36

I'll try that. Thanks all.

Michael Stokley23:08:07

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

Michael Stokley23:08:18

part of what prompts my question is the pedestal error handling

Michael Stokley23:08:33

which does have a convenience function that dispatches based on class

Michael Stokley23:08:59

which suggests to me they are not accustomed to using maps instead of objects

Michael Stokley23:08:55

or maybe i'm misreading that error-dispatch function. maybe it is dispatching based on an unqualified key :exception-type in a plain map

alexmiller23:08:21

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

alexmiller23:08:58

when interop'ing with Java libs, you do need some strategy to also handle Java typed exceptions though

alexmiller23:08:49

the slingshot library takes ex-info and the map orientation a lot further (ex-info actually started there and was adopted by core)

alexmiller23:08:36

the https://github.com/cognitect-labs/anomalies lib may be a useful frame for categorizing errors

Michael Stokley23:08:01

this makes sense to me.

Michael Stokley23:08:13

i'll look at the anomalies lib, too.

seancorfield23:08:49

We use anomalies at work. It's a good set of basic error types.