Fork me on GitHub
Michael Stokley05:01:26

does this look idiomatic?

(def hash-maps [{:id 1} {:id 2} {:id 3}])

(defn get_ [key]
  (fn [map] (get map key)))

(map (get_ :id) hash-maps)

Michael Stokley05:01:47

can't use partial unless i want to flip flop gets arguments

Michael Stokley05:01:06

an idiomatic example of currying, i mean


You can use (map :id hash-maps)

Michael Stokley05:01:57

it's a toy example. just trying to think about how to curry in clojure

Michael Stokley05:01:45

but thank you! i didn't know you could handle hash maps like that.


if the function is only used for that map, you can also just do (map #(get % :id) hash-maps) or (map (fn [m] (get m :id)) hash-maps) (I’m assuming you don’t always use keywords, since if so you could just do (map :id hash-maps) as indicated above)


I think curry technique in clojure cannot be used completely.


With partial function (Trying to do "Curry") you can do (map (partial :id) hash-maps), but I think it's not an idiomatic example


@michael740 Using anonymous functions is idiomatic: #(f % arg2)

Daniel Hines15:01:33

Clojurians seem like perceptive folk, and I was hoping to get some advice from some more experienced devs on how I should structure the data for an app I'm writing. Is this an ok place to solicit such?


I’d say go for it.

Daniel Hines16:01:20

I work on line-of-business apps for a call-center. Our agents rely heavily on a note-taking app I wrote that goes and gathers all the relevant notes from various sources in our CRM and displays them in a uniform format. They can also add notes by filling out various forms in the app. Some the data generated by the forms is arbitrary text input, and some of it is constrained to predefined options. Submitting the form has side-effects in the CRM that are essentially arbitrary. There are also complex rules on user-note ownership and editing. My current code-base for solving all these problems is getting exponentially worse as the requirements increase. My question is: how do I structure the data for these notes in way that makes my life easy, so that I can write arbitrary rules about the forms that generate them and the side-effects they have in other systems?

Daniel Hines16:01:07

My current thought is to maybe have some sort of tagging system for the notes that embues them with all the atributes the various side-effects will need. I could store all the notes in a Datomic database, and then have other services read each note when it's created, look at the tags, and hand it off to whatever functions handle those tags. That seems like it could get really complicated as well, but I'm not sure how else to handle it.


fwiw my mental model wouldn’t accept “the attributes the various side-effects will need” - a side effect shouldn’t be a first class part of the design, it should be an edge as it is an adaptor between your code’s explicit model and something that you can’t know or control. So I would replace that with “attaching tags to each entity so that they can be categorized, dispatched, or filtered by tasks that need to launch various side effects”


maybe that’s a pedantic point, but I find it useful to be very strict about never thinking of a side effect operation, or a thing that performs them directly, as being in the middle of my code - it has to be an edge as it interacts with a black box that is guaranteed to fail intermittently


but yes, that sounds like a good fit for a system in clojure that uses datomic - creating entities linked to an id representing a specific case number or customer id, with various attached tags that can be used to select the data needed for various operations / api calls / etc.

Daniel Hines22:01:26

Thanks for the feedback. I agree with what you're saying, and by no means want the side-effect code to be in the client application (which is where it is now). I'm going to have to brainstorm a bit to figure out how to express the problems I'm trying to solve in a way that is... solvable. Do you the Datomic forum is a better place for discussion about application architecture?


if the questions are datomic-specific, I think so - I’m not a datomic expert but I’ve been developing production clojure apps as my day job for years now

Daniel Hines22:01:22

Cool. I appreciate the willingness to help!


Say I have 2 seqs:

(def images ["2.png" "5.png" "0.png" "3.png" "1.png" "4.png"])

(def layers [[[4 5] [5 5] [6 5]]
             [[4.5 5] [5.5 5]]
             [[5 5]]])
and I want to write a fn such that (fn layers images) returns:
[[[4 5   "2.png"] [5 5   "5.png"] [6 5 "0.png"]]
 [[4.5 5 "3.png"] [5.5 5 "1.png"]]
 [[5 5   "4.png"]]]
. How would you go about this? I tried:
(let [i (atom 0)]
  (for [layer layers]
    (for [tile layer]
      (let [img (nth images @i)]
        (reset! i (inc @i))
        (conj tile img)))))
but for handles the sequences starting with the rightmost, hence not giving me the order I wanted, e.g.,
(([4 5   "2.png"] [5 5   "3.png"] [6 5 "1.png"]) 
 ([4.5 5 "5.png"] [5.5 5 "4.png"])
 ([5 5   "0.png"]))


loop/recur might seem a better choice but am perhaps missing something simpler.


@gamecubate you can just use map with multiple inputs: (mapv conj (apply concat layers) images)


My sequence flattened if I do that, though:

[[4 5 "img-2"] [5 5 "img-5"] [6 5 "img-0"] [4.5 5 "img-3"] [5.5 5 "img-1"] [5 5 "img-4"]]
instead of the desired:
[  [[4 5 "img-2"] [5 5 "img-5"] [6 5 "img-0"]]   [[4.5 5 "img-3"] [5.5 5 "img-1"]]   [[5 5 "img-4"]]  ]


Preserving the structure with:

(mapv #(mapv conj % images) layers)
almost works:
[[[4 5   "img-2"] [5 5   "img-5"] [6 5 "img-0"]]
 [[4.5 5 "img-2"] [5.5 5 "img-5"]]
 [[5 5   "img-2"]]]
Need to figure out a way then to drop images as I go.


what does the nesting represent?


Clojure is not very good at dealing with mutating data in nested structures


is it possible to convert the data into a vector of maps for instance?


that would be much easier to deal with


@schmee Yeah in that case a stateful walk would likely be easier in a lot of languages.


You can use loop/recur to do it. It's not too bad:

(loop [images (seq images), layers (seq layers), out []]
    (if layers
      (let [layer (first layers)]
        (recur (drop (count layer) images)
               (next layers)
               (conj out (mapv conj layer images))))


There is a hackier solution that's likely simpler, but involves a stateful iterator


@schmee The nesting represents tile coords on a 3-layer board. Tiles have images assigned to them from a shuffled set.


Was thinking about doing a refactor in favour of maps indeed. My first take is due tonight so would add this to a later sprint.


ahh, yeah, then a vector of {:x 0 :y 0 :z 0} or something would be much easier to deal with


Meanwhile, and thanks a lot @rauh but that looks right. You OTOH made it look too easy.


You’ve convinced me. Thanks.

Drew Verlee18:01:20

In cases where nesting makes sense. Then i suggest looking at the Specter Library. Which i understand, makes use of an abstraction that others say is similar to lenses.