Fork me on GitHub
#clojure
<
2019-09-14
>
quadron05:09:34

is there a core function that concatenates keywords? (??? :foo :bar) => :foobar

olle05:09:20

You can use something like this? (keyword (reduce str (map name [:foo :bar])))

quadron05:09:41

that's right, but I felt it would be natural to have a straightforward function in the standard lib

dpsutton05:09:18

What’s the usecase. I can’t imagine a time I would have ever needed this

4
quadron06:09:34

I am writing a function that "flattens" a hashmap of hashmaps by transforming the key of the inner hashmaps into a namespace: {:hello "world" :foo {:bar "baz" :ham "spam"}} -> {:hello "world" :foo/bar "baz" :foo/ham "spam"}

quadron06:09:10

the purpose is to be able to treat a hashmap as an applicative functor and simply do algebra with it.

andy.fingerhut06:09:24

I am 99% sure that there is nothing in the Clojure core library to do this. keyword, name, and string manipulation functions are the building blocks that exist to do it, though, without a lot of fuss.

andy.fingerhut06:09:26

Note that keyword can take 2 arguments, to create a namespaced keyword.

quadron06:09:26

yeah, the detour through another type is just confusing though. unless there is something I am missing. :thinking_face:

andy.fingerhut07:09:34

It gives you lots of power to go through that other type, given that it already has a big set of functions that can manipulate them (i.e. strings). If you tried to reproduce some or all of those for keywords, why stop at only concatenation? Why not sub-keywords by index into the name? Why not starts-with? ends-with? includes? and regex matching on keyword contents?

andy.fingerhut07:09:21

You could reproduce 10 to 20 functions that work on keywords directly, or "convert to string", do whatever you want, then "string to keyword".

quadron08:09:58

well I agree. I guess another way would be to write a function that makes any string function work on keywords?! It might only be a matter of aesthetic taste as to which is more readable. I'd prefer not to muck with the presented data if possible.

andy.fingerhut08:09:22

I am not sure if I understand your meaning, but one interpretation of your original goal is that you want to muck with the data.

andy.fingerhut08:09:08

you are given some collections that contain some keywords in them, and you want to change those collections to contain different keywords.

andy.fingerhut08:09:53

but yeah, one could write a function that takes a string function, and wraps it inside of a "convert keyword args to strings, then perform the original function, then convert the strings results back to keywords". Cool thing: you can do this yourself if you want it -- it doesn't need to be part of Clojure.

andy.fingerhut08:09:43

one view of software development is to build up the system you are given, layering functionality on top so it is more natural for solving the problem at hand.

quadron08:09:24

yes, but I am hiding this mucking operation too. the main idea is remaining true to the natural presentation of data structures.

quadron08:09:41

It's just a matter of keeping the code declarative and readable.

andy.fingerhut08:09:23

Sure. It seems likely that writing one or two functions that take one or two keywords, and return a new keyword, in the way that you wish for your code, could isolate the keyword->string and string->keyword conversions to those few functions.

lodin18:09:44

I have a higher-order function where caching is abstracted into two arguments, fetch and store!. I could check if fetch is not nil and let that represent that there is no value cached, but nil is a valid value and I don't want to recompute nil if it indeed is cached. (The problem is kind of like to how you'd use find instead of get if you need to distinguish between nil value and missing key.) I could require that fetch wraps the return value in a vector or a map, or even any type of reference (e.g. atom). Or I could pass a special value as an extra argument to fetch which represents that the value is missing, and check for that. Which would be your preferred way?

p-himik18:09:47

Clojure's memoize uses a sentinel value for that. It's a pretty common pattern. In CLJS its definition looks like (def ^:private lookup-sentinel (js-obj)). Your fetch function could return something like that - something that you know cannot be a part of the domain data.

lodin19:09:46

I was leaning towards passing in an extra argument, so I'm going with that. (I prefer that over defing a value, but I can't really argue well for it. :-))

p-himik19:09:56

Sure. But if you see something in the core of Clojure, it's usually there for a good reason. 😉

lodin19:09:53

To clarify, I meant that instead of having the value publicly as a var, I pass it as an argument. I might anyway def the value to not recreate the unique value on every call (not that I think it would matter the least in this case).

lodin19:09:58

If nothing else, I don't have to name the value, the author of fetch can call it whatever. 😉

p-himik19:09:53

Oh, but it's not a public def. Note the ^:private tag. You can also wrap your defns in a let that gives the values a very narrow scope.

p-himik19:09:14

Well, you still have to name the argument.

lodin19:09:02

I just pass the value, fetch is provided by the user. I'm one user, but not necessarily the only one. 🙂

lodin19:09:00

I don't seem to find the sentinel value that you talked about. memoize uses find on a map stored in an atom.

p-himik19:09:29

Ah, then the implementations of CLJ and CLJS are different, my bad.

p-himik19:09:37

Another thing - if you provide a static sentinel value, you could write a function like exists? that checks the value returned by fetch. If it returns false then you need to compute the value and call store!.

Al Baker19:09:33

is there a handy way to list all scheduled jobs in quartzite?

emccue21:09:41

in the clj-fx examples there are a few places where you feed the (deref state) into a function and then reset! the result of applying a function to that state

emccue21:09:05

im a bit confused by that

emccue21:09:32

since an atom is supposed to have compare and swap semantics

emccue21:09:47

this kinda feels like we are skipping over that whole step

emccue21:09:21

so if i have another thread updating the state and an action goes off im now no longer certain i wont lose stuff

emccue21:09:36

(i can do it with swap! obviously, but its no longer clear to me how exactly effects should work)

vlaaad21:09:22

can you point me to example?

emccue21:09:03

from the readme

vlaaad21:09:33

You raise a valid concern, this is actually not ideal. I tried to emulate re-frame, which is cljs, hence, single-threaded.

vlaaad21:09:58

You can create :swap-state effect:

(fx/wrap-effects handler {:swap-state (fn [f _]
                                        (swap! *state f))})

vlaaad21:09:14

Your handler then will be like that:

(defn handle [event]
  (let [{:keys [event/type text state]} event]
    (case type
      ::add-todo {:swap-state #(update % :todos conj {:text text :done false})})))

emccue21:09:42

this is what i've done so far

emccue21:09:09

im still a bit fuzzy on how effect handlers should work in general so i might need to read up more

vlaaad21:09:58

Well I gave you example above 🙂

emccue21:09:59

i know in the simpler model of elm I would get the state as something i could consider when making a decision about effects

vlaaad21:09:17

you remove deref co-effect and use :swap-state effect

emccue21:09:27

so in this case with the :swap-state that makes sense

emccue21:09:55

but lets say, as a toy example, i wanted to send an http request if my "count" is even

emccue21:09:09

or if a flag is false

emccue21:09:24

yeah flag is some state is better

emccue21:09:55

with the swap-state approach I can have a function that updates my state based on the "current" state of the flag no problem

emccue21:09:08

if i am out of sync i will go through the atom thing

emccue21:09:36

but the state i consider when deciding to make an effect can be out of sync with the state i consider when deciding what my new state will be

emccue21:09:50

since i would end up needing to deref the atom in my event handler to know what to return in that effects map

emccue21:09:04

does that make sense?

emccue21:09:53

I don't have any easy answers or I would make a PR, but its something to consider

vlaaad21:09:50

I have somewhat easy answer: don't swap/reset state atom you use in cljfx from different threads

andy.fingerhut21:09:03

I may be off base here, but this sounds like it might be a fundamental issue in any multi-threaded program where multiple threads wish to sometimes make updates to a centralized state. If they get a snapshot of the current state and compute on it for a while before doing some kind of mutual exclusion to do an update, then that snapshot can in general be "old" by the time you enter the critical section.

vlaaad21:09:33

how to do it in a thread-safe manner: - wrap event handler using fx/wrap-async - don't use atom, send event maps to wrapped event handler.

vlaaad21:09:21

wrap-async will make event handling go through agent, so it will happen sequentially, but it will be safe to call from multiple threads

vlaaad21:09:29

@andy.fingerhut yeah, I think agents are a good solution here

andy.fingerhut21:09:47

Even update functions given to swap! can be redone N times in general, if other threads finish their swap! calls before you do.

andy.fingerhut21:09:38

Well more precisely, if they update the state of the atom after you read it, but before attempting your compare-and-swap inside the swap! implementation.

vlaaad21:09:47

@emccue don't forget to read docs about fx/wrap-async if you don't know how agents work, there are some peculiarities

vlaaad21:09:34

And just to make it clear: if you use wrap-async and don't use state atom from different threads, it is safe to use deref co-effect/reset effect in event handling

emccue22:09:32

yeah - ill probably have more questions as i get deeper in

emccue22:09:51

im planning to use clojure for a software dev. class this semester and they have a requirement for a desktop gui

emccue22:09:00

at some point down the road

vlaaad22:09:18

I think #cljfx channel might be more fitting for questions about cljfx, I'm there as well

emccue22:09:10

oh there is a channel - cool