Fork me on GitHub

  (map (fn [x]
           (and (vector? x) (< 2 (count x)))
           ;if true, transform here 
           ;grab first and last
           (let [tag       (first x)
                 last-item (last x)]
             ;build the transformed vector
              ;the new string
                " of the "
                (->> x
                     (drop 1)
                       (fn [accum item]
                         (if (vector? item)
                           (conj accum (second item))
                           (conj accum item)))
                     (interpose " of the ")
                     (apply str)
           ;else return unchanged
        [:ni-jar 'sensei]
         [:no-jar 'gakkou]
         [:no-jar 'sensei]
        [:verb 'sabishii]]))


=> [:S [:ni-jar sensei] [:ga-jar “hanashi of the sensei of the gakkou”] [:verb sabishii]]


You are incredible! whaaat let me take a look at this


it is somewhat simplistic, and I’m not sure it covers all cases, but whatever we talked about it does… it is very simplistic in the sense that the way it checks whether to transform is via (and (vector? x) (< 2 (count x)))


But I went by your phrase “Essentially any vector that has more than two elements [:key and val] needs to be smashed to have just key and val.”


So for your data structure, at this limited level of nesting, that should cover the requirement…


that's very good. i'm trying to do it in cljs and it is complaining there is no rseq? ... hmmm. i very much appreciate your help & efforts!


sorry, reverse


(let [transformer
      (fn [x]
        (let [tag       (first x)
              last-item (last x)]
             " of the "
             (->> x
                  (drop 1)
                    (fn [accum item]
                      (if (vector? item)
                        (conj accum (second item))
                        (conj accum item)))
                  (interpose " of the ")
                  (apply str)

  ;run it
    (map (fn [x]
             (and (vector? x) (< 2 (count x)))
             ;transform here
             (transformer x)
     [:ni-jar 'sensei]
      [:no-jar 'gakkou]
      [:no-jar 'sensei]
     [:verb 'sabishii]]))


Second one is a bit cleaner since the big blob of code is separated in a function…


and quite clean. thank you. i gotta take a good look at it


There is one more thing


it can be :no-jars or :to-jars .. they work the same way, but don't mix.


Ahh… well 🙂


i can probably add those conditionals


What do you mean by “don’t mix” ?


like if there is more than one :no-jar per squad, i can run the one transform, and if there is more than one :to-jar i can run a different transform, but all i need to do is make your one transformer into two


Let me give you an example


I guess it's ok if they mix, but the :to-jars should render to a different string: input: [:S [:ga-jar [:to-jar hakujo] [:to-jar yata] seuss] [:verb iru]] desired output: [:S [:ga-jar "hakujo and yata and seuss"] [:verb iru]] So if there were a way to distinguish transformers that would be cool


Thanks for your patience 🙂 my brain is mostly melty today


So like the difference is the concat word?


:no-jar -> ” of the ” :to-jar -> ” and ”


(let [transformer
      (fn [x]
        (let [tag        (first x)
              last-item  (last x)
              any-jar    (-> x (second) (first))
              concat-str (condp = any-jar
                           :no-jar " of the "
                           :to-jar " and "
                           " hmmm ... ")]
             (->> x
                  (drop 1)
                    (fn [accum item]
                      (if (vector? item)
                        (conj accum (second item))
                        (conj accum item)))
                  (interpose concat-str)
                  (apply str)

    (map (fn [x]
             (and (vector? x) (< 2 (count x)))
             ;transform here
             (transformer x)
     [:ni-jar 'sensei]
      [:to-jar 'gakkou]
      [:no-jar 'sensei]
     [:verb 'sabishii]]))


It starts to smell in the sense that now you’re expecting a certain shape of your data structure… if it’s guaranteed to be this shape, then OK… but if it’s not… but it does work for this specific case


Yea we can do it… the data structure here starts to smell to me… but you can do it


That's really wonderful, yeah it's pretty much guaranteed to be this way


the data shape i mean


Again, a lot of assumptions here for the data shape… if you get something in a weird shape, chances are this will throw an error or not work as expected


If that’s the case, then we’re good 🙂


the interface that generates the data is buttons you click, so it's very hard to evade the rules :2


thanks a lot man

✌️ 4

Ideally, I would find a way to group things by :to-jar or :no-jar so that keyword appears once, rather than just grabbing the second item of a vector and then looking at the first item of that sub-vector


That’s the “smell” I’m talking about


any-jar    (-> x (second) (first))


This is the “smell” 🙂

😄 4
🎯 4
Endre Bakken Stovner06:05:15

Are there patterns for threads (actually processes) updating an atom when they finish? After the update is finished it should trigger some function. Is the best way to let the process know about the data structure it should update? And then have a watcher on the data structure which triggers the function? I'm sure I could remove some complection with channels, but it makes everything non-deterministic.

Endre Bakken Stovner06:05:20

Yes, thanks 🙂 But I am trying to ask whether there are other and better ways of solving my problem.

Tzafrir Ben Ami07:05:06

Using channels maybe? Signal the channel (put) when thread is done and have a different thread park on the channel for output (take)

👍 4
Ben Sless08:05:18

if we think about it as a system of processes (going the CSP route), let's imagine a controller process C, and a worker process W. you can implement a sort of async RPC where the controller sends messages to a worker and the worker acts on them and returns a message via a response channel, i.e. C!cmd -> W?cmd; do-stuff; W!resp -> C?resp When you have several workers you can just index the response and control channels

Ben Sless08:05:02

Diving a bit into the worker, it should itself contain a process that does the actual work and it will be the "interface" to it, which queries and reports if it's completed

👍 4

the future macro creates a new thread, and returns a promise that won't be ready for reading until the thread exits, and returns what the code in the future returned on deref


CSP is cool but also overkill for the literal feature asked for here

Ben Sless15:05:16

Maybe it is... I've been immersed quite deeply in it lately so I feel quite comfortable with the abstraction and semantics. Why do you think it's overkill?


core.async (the official implementation of CSP for clojure) is quite "leaky", and imposes many assumptions / restrictions on your code reading more carefully, one could implement what you describe with thread safe queues, and that would avoid many of the problems (and impose the problem of maintaining your own channel infrastructure)


the specific behavior asked for is easily built out of future and deref

Ben Sless15:05:09

then you'd use watchers and callbacks to update the shared data which will be accessed by the "supervisor" thread?


no, I'd use futures and deref:

(def state (atom {... ...}))

(def process-a (future (swap! state frob x y)))

(def process-b (future @process-a (swap! state flux z)))


the deref of proces-a ensures process-b doesn't execute the rest of its body until process-a completes, and propagates any failure to interrupt it as well


of course in real code I wouldn't put future calls in def - these would be let bindings

Ben Sless16:05:46

wrt core.async being leaky, It's something I find with all the rich abstractions (promesa, manifold, callbacks), they all leak and infect the code and you have to write in them


that's a separate problem: you need to lift other code into the abstraction to cooperate with it


by leaky, I mean that with core.async you need to: • ensure that no CPU intensive or blocking calls happen in go blocks • ensure that a given block doesn't read and write the same channel in a loop • avoid certain macros inside go blocks that the callback transform compiler can't handle etc. things that aren't just the async paradigm, but limitations of how it's currently implemented

Ben Sless16:05:49

Ah, I see what you mean. Yeah, there's a lot of discipline(?) required when working with core.async. With future/deref, how would you implement a DAG of tasks which you also want to be able to monitor? also you might want to kill, restart, etc.


well, killing of processes inside the jvm simply isn't a thing, unless done cooperatively by checking for a cancellation flag


regarding task graphs, I don't know of a good solution in clojure off hand, aside from plumatic/graph which I haven't used in anger and can't vouch for


usually people go for task distribution DSLs (which then offer distribution across multiple servers etc.) but once again, I didn't infer the need for that kind of feature from the original problem description


generally all of these things are methods of making your code less reliable and harder to debug, I wouldn't adopt any of them unless they were absolutely required for your domain

Ben Sless16:05:44

The OP is the simplified problem description, I have prior knowledge from a post a few hours prior (that may be cheating 🙃 ) I've been thinking about similar problems lately, I'm leaning towards separating execution model from data model, then it's possible to try out different solutions and break them apart to isolate the fragile/unreliable parts as much as possible


big picture, it's usually the breaking apart that makes things harder to code correctly

Ben Sless16:05:55

Interesting. Why?


when you turn procedural step-by-step code into a graph of callbacks, processes, and conditions, what would be trivial surface level bugs become non-obvious


also, the call stack, which reveals the source of erroneous behavior, typically loses all meaning or utility


then the desire to describe processes as config rather than code, further detaches the erroneous state from any of the language facilities that help us ensure things are correct - a typo on a keyword doesn't throw a compile time error the way a var typo does, etc.


these tradeoffs can be worth it, but they have a huge upfront cost in code quality and maintainability that you need to repay


which is why I suggest adopting as little distribution and async as you can get away with


on top of everything else, these indirections make code slower, and the paralellism they enable doesn't always compensate

Ben Sless17:05:44

I see. Thank you

Ben Sless17:05:00

With what you said about a keyword error, that's a pertinent point, which I think is remedied a bit by the model separation. when you "compile" the data model you can overcome at least that issue


that's true, and I don't mean to imply that these techniques are never appropriate, or that we don't have good tools to reduce the problems listed


when I first learned how core.async worked I was excited about what it offered, and used it liberally in my codebase this led to very subtle and hard to debug intermittent problems, partially due to my incomplete understanding of core.async and partly due to the limits of that model definitely use it if you need async execution, and you need complex patterns of coordinating results, but it doesn't come for free

Ben Sless17:05:57

I mostly do processing of infinite streams of data, so I use pipelines quite often

Ben Sless17:05:14

not so much the more complicated coordination semantics


Is it okay to create a dummy namespace just to be able to use namespace qualified keywords in that namespace? Are there other ways to achieve the same effect? I know :dummy.namespace/keyword works, but I want to avoid typing the full namespace every time.

Alex Miller (Clojure team)12:05:21

It’s ok. You can do it with create-ns and alias in the place where you need it


Great, thank you for confirming.

Daniel Östling10:05:10

What's the standard way of returning only the first regexp match? I realize re-find uses re-group to return results, but is it then safe to assume re-group always returns the input as first result?

Daniel Östling10:05:27

> (re-find #".*[?&]some-param=([^#&]+)" "?some-param=some-val")
["?some-param=some-val" "some-val"]


If by 'first' you mean 'only one match, not multiple matches', then yes that is safe to assume.


If by 'first' you mean 'guaranteed to be the earliest match of the regex in the string', that depends on the regex and the string, perhaps. I don't have examples handy to demonstrate possible surprises there.

Daniel Östling10:05:32

Yeah, I read on docs for re-groups that "

If there are
nested groups, returns a vector of the groups, the first element
being the entire match.

Daniel Östling10:05:53

But for that example I had above, does that really count as a nested group?


In that regex you give, for example, does the initial ".*" part need to be there, really?

Daniel Östling10:05:19

Well, I'm counting on input being possibly a full URI

Daniel Östling10:05:39

And I'm looking to extract only specific query parameter values

Daniel Östling10:05:57

I guess ^.* could have been better


In the regex, any parenthesized expression is a capture group, unless it begins with a special character sequence, IIRC something like "?:" just after the left paren


re-find looks for any match of a substring that matches your regex, so using re-find either ^. or . at the beginning are redundant, I believe.

Daniel Östling10:05:48

Hm. I need to read the regexp docs again 🙂


re-matches restricts itself to only matching the entire string.


The Clojure doc strings for those functions refer to Java APIs for the full behavior.


and those Java docs are not short 🙂

Daniel Östling10:05:19

Perhaps reasonably, regexp isn't a small topic 🙂

Daniel Östling10:05:39

I will think on what you wrote, thanks 🙂


If anyone has a question, feel free to ask it 😃


Hi fellow clojurians, I'm trying to read a file from the resources folder from the repl with `` . I'm using deps.edn and have added resources to the paths vector interestingly io/file works


(defn load-config [resource profile]
  (-> resource
      (get profile)))


the call to io/resource always returns nil

Daniel Tan13:05:51

you should probably use the relative path from the resource folder


@dev-hartmann make sure the resources folder is on the classpath


:paths ["src" "resources"] in deps.edn


@ghadi thx, that fixed it i had :path instead of :paths facepalm


Hey all 👋 Does anyone know why 4clojure is screaming at me not to use count ? problem 22 I submitted and it says You tripped the alarm! count is bad! for the following answer:

(fn [x] (reduce + (for [position x]
                    (+ 0 1))))


for must use count inside the expansion of that form


yeah, for is pretty wild

user=> (macroexpand-1 '(for [position x] (+ 0 1)))
(clojure.core/let [iter__6305__auto__ (clojure.core/fn iter__140 [s__141] (clojure.core/lazy-seq (clojure.core/loop [s__141 s__141] (clojure.core/when-let [s__141 (clojure.core/seq s__141)] (if (clojure.core/chunked-seq? s__141) (clojure.core/let [c__6303__auto__ (clojure.core/chunk-first s__141) size__6304__auto__ (clojure.core/int (clojure.core/count c__6303__auto__)) b__143 (clojure.core/chunk-buffer size__6304__auto__)] (if (clojure.core/loop [i__142 (clojure.core/int 0)] (if (clojure.core/< i__142 size__6304__auto__) (clojure.core/let [position (.nth c__6303__auto__ i__142)] (do (clojure.core/chunk-append b__143 (+ 0 1)) (recur (clojure.core/unchecked-inc i__142)))) true)) (clojure.core/chunk-cons (clojure.core/chunk b__143) (iter__140 (clojure.core/chunk-rest s__141))) (clojure.core/chunk-cons (clojure.core/chunk b__143) nil))) (clojure.core/let [position (clojure.core/first s__141)] (clojure.core/cons (+ 0 1) (iter__140 (clojure.core/rest s__141)))))))))] (iter__6305__auto__ x))

👁️ 4

Oh wow I did not know you could do that


with a for that basic, the map equivalent should be easy to write


(I mean I'd write it now but this is supposed to be a learning exercise after all :D)


Haha no worries…Thanks for pointing it out and macroexpand trick


Will attempt another way


another alternative is using a function other than + in reduce, since you are using reduce already

👌 4

I've found myself knee deep in a conformed spec data structure and I want to use match to couple destructuring + binding (Erlang style, I suppose). Should I be keeping it separate (the code is fairly trivial) or should I bring in something like core.match?


Destructuring already does binding?


Do you mean you want to couple destructuring + control flow ?


(let [{:keys [a b]} {:a 1 :b 2}]
  [a b])
;;=> [1 2]

Ben Sless18:05:09

You might like this lib, it allows matching like multimethods with "wild cards"


Yeah, destructuring + control flow was what I meant


Thanks Ben, I'll take a look


the main arguments I've heard against match: • it leads to brittleness that prevents late binding (RH advocates using multimethods so other code can add cases as needed) • it produces large bytecode, and since the JVM imposes a hard limit on bytecode size, combining it with eg. core.async or for or core.logic can lead to compilation errors


all that said, it's a style choice, there are reasonable ways to avoid those issues, use match if it is the idiom you want for your codebase


Ah, thanks


I think in the case I won't use it as it's just for style purposes on internal code I don't care too much over


I'm surprised I've made it this far without reaching for it, actually


I heard good things about Meander as well, wonder if it generates smaller bytecode.


I kind of doubt it, where core.match runs into trouble is it tries to an algorithm for compiling pattern matches that was created for compiling to machine code (or some lowlevel bytecode) that assumes the presence of things like 'goto', so core.match either has to duplicate code or emulate goto (in theory using a preallocated exception on the jvm does this well, which is how I think it does it)


meander looks like it similarly tries to be an optimal compiler for pattern matches (I am not 100% sure but a quick glance leads me to suspect it is preallocating exceptions for jumps as well), so I suspect it may hit the same issues as core.match


but you can write a (dumb, with no features) pattern matching macro in less than 200 lines of code, which likely will not have those issues

Jimmy Miller18:05:50

We don't actually compile to exceptions for jumps in meander. I don't know for sure if the problem that's bring talked about is a good fit for meander or not. But so far I've never seen a meander match that runs into the code too large problem. And we have some fairly complex and long matches.

👍 4

what is all the cata stuff then?

Jimmy Miller18:05:08

Those turn I to recursive function calls. You can definitely blow stack if you aren't careful.


Its a crappy hack and yes it is, however, its only used for rewrite . The matching is compiled to letfn and we invoke those functions instead of throw to emulate goto.

Jimmy Miller19:05:57

That isn't a go-to in the sense you are talking about as far as I can tell. It is just a hack around the fact that if something fails we throw an exception. So when we cata we need to catch the exception and return a value. We aren't jumping to different cases.

👍 4

Ah, yeah, duh.

😂 4
Jimmy Miller18:05:01

But never been a problem in practice. We are even writing the next version of meander using the current version and have had no scaling issues.

Jimmy Miller18:05:51

But given that this is #beginners. Not recommending everyone go off and use meander right away. You can check out my talk or blog posts if you wanted to learn more. But it is something that takes a bit to learn.


Thanks for your input. In my case just pulling a library for what I wanted was overkill but I can also see how it tightly couples the data. So the Clojure point of view is worthwhile 80% of Clojure seems to be learning the tools / libraries so I'll skim the docs 🙂


Found a talk -- it was fun! I might have found a new hammer

Jimmy Miller22:05:38

Glad you liked the talk. :) Feel free to ask any questions you have on #meander.


I am honestly struggling to convert this (reduce + (map (fn [message] (+ 0 1)) [[1 2] [3 4] [5 6]])) to the koan style of submission 😔 where [[1 2] [3 4] [5 6]] is meant to be any sequence


@mauricelecordier as that stands it just counts how many elements are in the sequence -- is that what you intend?

user=> (reduce + (map (fn [message] (+ 0 1)) [[1 2] [3 4] [5 6]]))


Needing to count the elements without using count an earlier submission I used, used for which uses count internally and got rejected


Yip! Its problem 22 in 4clojure


why (+ 0 1) and not just 1?


That seems to error out


Did you have (1) instead of just 1 perhaps?


(1) means "call 1 as if it were a function" which with throw a class cast exception (because Long -- the type of 1 -- is not IFn -- the expected type of a function)

💯 8

(reduce + (map ,,, <some-sequence>)) => ( #(reduce + (map ,,, %)) <some-sequence> ) is the transformation from an expression containing some sequence to an anonymous function of one argument applied to that sequence.


@mauricelecordier You might find constantly useful here...


I see this has an example using constantly as a way to find the size of a collection…thanks! Thats interesting


...but you don't need to map over the elements really since you don't care about their values: consider (reduce (fn [n _] ???) 0 <some-sequence>) for some function ??? as a possible solution shape (the _ is a convention for "I'm deliberately ignoring this argument").


n will be 0 on the first call. What would you need ??? to be for that to count the elements?


Or just (inc n) which is more idiomatic.


True….I am still getting use to the language itself


Don't worry -- becoming idiomatic takes a lot of practice: clojure.core has hundreds of functions that you slowly get used to combining.


I am about to start chapter 4 of the Brave book which deals with clojure.core a bit more

Mario C.22:05:07

When using the lein shell plugin I keep getting WARNING: You have $CLASSPATH set, probably by accident do I just have to live with this? I believe this is happening because the plugin is mostly likely executing a lein command in another session(?) and it inherits the classpath from the original lein process. But honestly have no idea


in your shell type "echo $CLASSPTH"

Mario C.22:05:12

Returns nothing

Mario C.22:05:31

:aliases {"run-client-external" ["shell" "./scripts/" "external-local"]}

Mario C.22:05:31

The scripts sets an env variable and then runs lein run-client


the number of jvms you must launch when you do a build must be pretty impressive

Mario C.22:05:54

I am not sure to be honest


me neither, but since you are running a shell script anyway you can unset CLASSPATH in that shell script

Mario C.22:05:37

Oh good idea, thanks!