Fork me on GitHub


metal 1
Lyderic Dutillieux09:11:47

Hey guys, I'm looking to send emails with Clojure. I'm new to the Java eco-system. I saw that there is this clojure lib (postal :, which wraps Jakarta Mail. Anyone have an opinion on Postal or Jakarta Mail ? Thanks 😉


Another one to evaluate is it’s pretty simple.

👀 1

However, for the moment, I’m mostly sending emails via Amazon SES. Works great (with the cognitect aws libraries)


Given a collection like:

[{:lname "ln", :fname "First user", :email "", :mobile "+1111111121", :secondarychannels [{:m "email", :mv ""} {:m "email", :mv ""} {:m "sms", :mv "+1111111111"}]} {:lname "ln", :fname "Second user", :email "", :mobile "+1111111112", :secondarychannels [{:m "email", :mv ""} {:m "email", :mv ""} {:m "sms", :mv "+1111111131"}]}]
I want something like: {:emails [“<mailto:[email protected]|[email protected]>” “<mailto:[email protected]|[email protected]>” “<mailto:[email protected]|[email protected]>” “<mailto:[email protected]|[email protected]>” “<mailto:[email protected]|[email protected]>” “<mailto:[email protected]|[email protected]>”], :sms [“+1111111121” “+1111111111" “+1111111112” “+1111111131"]} There are multiple ways to do this and I have a working approach of first gathering all the emails and then gathering all the mobiles and merging them together. I’m looking for an alternate solution that reads like:

let x = {:emails [] :sms []}
for every element in collection->secondarychannels, if m == email, append mv to emails collection (in x), else append mv to sms collection (in x).
What is a good way to write the same in Clojure?

Bob B14:11:14

you could group secondarychannels by :m and then use the values I believe

✔️ 1

There’s a library called meander that was written for this sort of thing. If you’re new to Clojure it may or may not be a good fit, but you might be interested anyway.

✔️ 1

Your pseudocode seems to easily translate to a reduce

✔️ 1

I played around a bit for fun and ended up with with this:

(->> stuff
     (mapcat :secondarychannels)
     (reduce (fn [agg {k :m v :mv}]
               (update agg (keyword k) conj v)) {}))
where stuff is your original structure


Unless you specifically need the ordering, sets seem like a better semantic fit for your two resulting collections.


It took me quite a while to find a data-shape that worked well and easily for updating/retrieving user information, and I once upon a time tried the following...

{1 {:email "[email protected]" :first-name "pi" :last-name "hax0r" :phone "1112223334"}
 2 {:email "[email protected]" :first-name "pi" :last-name "zax0r" :phone "44422233333"}
 3 {:email "[email protected]" :first-name "pi" :last-name "ex0r" :phone "5554446677"}
 4 {:email "[email protected]" :first-name "pi" :last-name "ox0r" :phone "3339998888"}
in that way you can find users by numerical index... But what if you want to search your users by e-mail? Well, you can use the e-mail as the "primary key" so-to-speak, which might take the shape:
{"[email protected]" { :first-name "pi" :last-name "hax0r" :phone "1112223334"}
 "[email protected]" { :first-name "chi" :last-name "zax0r" :phone "44422233333"}
 "[email protected]" {:first-name "psi" :last-name "ex0r" :phone "5554446677"}
 "[email protected]" {:first-name "phi" :last-name "ox0r" :phone "3339998888"}
So now you can look up user-detail-maps by their e-mail. Kind of like an array but the array index is actually an e-mail address. Anyway, I have found the latter datashape to be very useful. Always open to suggestions / optimizations 😃


I agree with @U4P4NREBY on that the set would be the right data structure for these kinds of operations. There are std functions for sets, like select, join and so on, which seem to be the right fit for the transformations you want to do.


I mean, what you're building here IS an index and this IS a relational database


@U06DX1ANS, that gives the result. Thanks!


@U4P4NREBY, @U01EFUL1A8M Ordering is not a concern. Can you help with an example on how I’d go about using a set to achieve the result? Thanks!


@U3ES97LAC, I’m not sure how I’d go about the structure you mention to achieve the resulting structure. The base-collection in the example is from an API response. Are you suggesting I transform the response to the structure you mention, and that should make the extraction of emails /phones simpler?


@U064X3EF3, Didn’t think of it that way. I can see the parallels now.


@U019M13JFAQ Disclaimer: I’m also just a beginner/intermediate Clojure programmer. But have a look here: My instinct is that you can define your collection as a set of maps (AKA kind of like tagged tuples in relational algebra). And then you want to use a combination of project select and index to achieve what you are doing. My assumption is that your code becomes a bit clearer, more declarative that way. To get the hang of it I suggest you open a REPL and just play around with these functions a bit and some example data (that’s what I would do).


I also happened to look into project, select and index on clojuredocs when I saw these comments. I wonder if there's a blog post/tutorial introducing these to people unfamiliar with SQL/relational algebra...


@UCW9TUDNK SQL is based on relational algebra. I learned it with the book Datenbanktheorie H.Buff (german). The basic operations and theory are rather simple. It’s based on set and tuple data structures. I’m not aware of an online resource, but I recommend learning the general first (relational algebra) and then apply it to SQL and/or Clojure’s sets.


Thanks for the info! I can't read german, but I'll try and find a book on relational algebra.


@U01EFUL1A8M, thanks a lot for pointing to the helpful resources. Will try out playing around with those commands. A few questions though: > AKA kind of like tagged tuples in relational algebra Not really aware of a concept of “tagged tuples” in relational algebra. Can you point to relevant examples?


@U019M13JFAQ what I meant is just tuples. The tagging occurs by giving each position a key so to say. You can think of a map (or record, row etc.) as a tuple, but with names for each position. There must be a better word for it, but I don’t remember it, so I just said “tagged tuples”. Similarly, a vector (mathematics) has positional semantics like (23, 7, 42); but we sometimes say x, y, z to refer to each position in the vector. Again, probably there are better words for this as well :)


@U01EFUL1A8M, got it. Thanks!


Anyone familiar with I’m having trouble finding the right function in the docs to convert a string timestamp to a more readable date-time format. Given a timestamp in ISO-8601 "2020-11-22T19:00:01.000939-06:00", how would I be able to convert it to something like “Nov. 22, 2020 07:00 PM” or even “2020-11-22 7:00 PM”?


the "format" and "formatter" from this ns are imported into the primary ns, and are made for this sort of thing


I have a hunch this library makes more sense if you are familiar with the underlying classes it wraps


(which kind of begs the question regarding using the library...)


there is a simple looking example in the README though

(format "MM/dd" (zoned-date-time 2015 9 28))
=> "09/28"


Thank you. I’ll try out format


Hi everyone, I'm trying to get started with compojure (after a few years' hiatus), and I'm having a hard time getting route/resources to work. I'm not even able to get .resource to work:

mkdir resources
echo 'hi' > resources/x
clojure -e "(require ') (prn ( \"x\"))"


Yep, if I use the filename hello.txt as in the clojuredocs example ( I still get nil


I think I got it, I wanted route/files instead of routes/resource. I'm not sure why, I guess I don't really understand resources


If you use route/files, you'll find your app won't work when you build an uberjar and run it (via java -jar).


I see, so that's more for accessing files on a target system then? So how do I get resource to work?


You need resources on your classpath to be able to pick up files from it. That's why running clojure like that isn't seeing it.


Ok, I'm just using tools.deps, not lein, so I assume that's something lein-ring handles for you? So it's probably just a flag I pass to clojure when I run my program


(! 729)-> mkdir jstaab
(! 730)-> cd jstaab/
(! 731)-> mkdir resources
(! 732)-> echo 'hi' > resources/x
(! 733)-> clojure -e "(require ') (prn ( \"x\"))"
WARNING: When invoking clojure.main, use -M
(! 734)-> clojure -Sdeps '{:paths ["resources"]}' -e "(require ') (prn ( \"x\"))"
WARNING: When invoking clojure.main, use -M
#object[ 0x34a1d21f "file:/Users/sean/clojure/jstaab/resources/x"]
^ like so


Or put a deps.edn file in that directory that has :paths ["resources" "src"] (assuming you also have a src folder)


neat, I'll try that


That did the trick! Thanks Sean, you remain the best.


clojure 1.10.1 for reference