Fork me on GitHub
#beginners
<
2017-12-04
>
Bravi00:12:05

@drewverlee I’m in the same boat 😄 have been trying to build a simple rest api since friday. it’s very complex I must say

Bravi00:12:03

I mean it might be easy once you learn, but actually making it work the first time..

Drew Verlee00:12:33

@bravilogy I just started and my needs are very basic. I'll probably spend longer reading about the ecosystem then it takes me to build one ... i hope. For what its worth, i like the approach Yada is taking, though im going to use compjure-api because my needs are so basic i might get more out of something less opinionated.

Bravi00:12:31

I started off with just a simple app template too. compojure + ring combo

Bravi00:12:19

it works great, but I found that it doesn’t automatically return json unless you specify so in every single handler’s headers {"content-type" "application/json"}

Bravi00:12:54

I’m looking for some alternatives at the moment, so if you find anything good please do let me know

Drew Verlee00:12:45

Im going to try https://github.com/metosin/compojure-api Which seems to have a bit more then compjure basic. It might let me re-use my specs, other wise i would probably use Yadda, or if i needed to do anything more then a single set of GET, PUT, POSTS!

seancorfield00:12:29

@bravilogy The content type should be set by wrap-json-response, which also converts the data structure to a JSON string.

Bravi00:12:57

@seancorfield unfortunately it doesn’t. I had to manually do it for every single handler. I do have wrap-json-response in my middleware list, but it doesn’t seem to be doing anything. Actually what happens is that, once I visit the url that’s supposed to display json, it actually downloads the content to my machine in a text format

seancorfield01:12:25

@bravilogy It works fine. I just tried it in the REPL.

seancorfield01:12:56

Your handler is missing a call to ring.util.response/response tho'

seancorfield01:12:59

boot.user=> (defn get-response [] {:body {:result {:this "is" :a 42}}}) ; this just fakes the `client/get` call
#'boot.user/get-response
boot.user=> (require '[ring.middleware.json :refer [wrap-json-response]])
nil
boot.user=> (require '[ring.util.response :as resp])
nil
boot.user=> (defn handler [req] (resp/response (-> (get-response) :body :result)))
#'boot.user/handler
boot.user=> (def app (wrap-json-response handler))
#'boot.user/app
boot.user=> (app {})
{:status 200, :headers {"Content-Type" "application/json; charset=utf-8"}, :body "{\"this\":\"is\",\"a\":42}"}
boot.user=>

Bravi01:12:07

then perhaps chestnut’s template has a problem 😞 this is my handler

(defn open-orders [req]
  (response {:data "hello world"}))
and this is the config:
(defn config []
  {:http-port  (Integer. (or (env :port) 8080))
   :middleware [[wrap-defaults api-defaults]
                wrap-json-response
                wrap-reload]})

seancorfield01:12:56

I think you need wrap-json-response before wrap-defaults

seancorfield01:12:46

Otherwise the wrap-content-type in wrap-defaults is going to set a default (and incorrect) Content-Type header. And that will prevent wrap-json-response from adding its application/json type.

seancorfield01:12:00

Middleware is very order-sensitive.

Bravi01:12:45

yeah, it worked 😄

seancorfield01:12:52

You should try it out in the REPL, like I did.

seancorfield01:12:11

(and we had this convo before and I suggested reordering the middleware, didn't I? 🙂 )

Bravi01:12:38

no I don’t think we did

seancorfield01:12:40

Anyway, glad you have it working now...

Bravi01:12:50

I usually try it with cider, but didn’t know how to test the middlewares

seancorfield01:12:08

They're just functions, composed around the handler.

Bravi01:12:11

I usually evaluate a function call straight away in there

Bravi01:12:35

yeah still figuring out 😄

seancorfield01:12:15

Getting used to the REPL-driven workflow takes a while. It's a very different way to work from most languages 😐

Bravi01:12:33

it is yes, and I like it very much

Bravi01:12:01

the fact that I can see the results straight away in my buffer here is amazing

seancorfield01:12:12

:thumbsup::skin-tone-2:

Bravi01:12:00

I wish there were more resources for clojure

seancorfield01:12:10

You mean more learning resources?

seancorfield01:12:47

I mostly learned from books, and trying stuff in the REPL -- and there are a lot more books around now.

seancorfield01:12:38

First two books I bought were Clojure in Action and Joy of Clojure.

Bravi01:12:10

might give them a try, thanks. I’ve always been more of a video course + stackoverflow learner

Bravi01:12:40

but since I’ve watched everything now 😄 I think I should go for books instead

seancorfield01:12:53

Eric Normand has some great video tutorials on http://purelyfunctional.tv

Bravi01:12:27

I’ve seen those, I’ve seen udemy, pluralsight ones and signed up to lambdaisland too

seancorfield01:12:00

Joy of Clojure has a 2nd ed out now -- and it's very much the "why" of Clojure. I'd say Clojure Applied is a good book for practical application building.

Bravi01:12:34

thank you 🙂

seancorfield01:12:59

Also, Programming Clojure 3rd Ed is in "beta" https://pragprog.com/book/shcloj3/programming-clojure-third-edition -- that's a pretty comprehensive coverage of the language.

Drew Verlee01:12:24

Im assuming live reloading is possible when working on a compjure-api app. This blog post seems to indicate as much https://www.anthony-galea.com/blog/post/getting-started-with-compojure-api/, however, if i edit my handlers and refresh the page it remains the same. Any suggestions on live reload of the api when working?

Drew Verlee02:12:40

nevermind. I was refreshing the wrong project 🙂

seancorfield02:12:34

@drewverlee Another useful trick it to use Vars for handler references -- #' -- so that you can just eval a single handler into the REPL (to redefine the function) without having to reload anything else.

seancorfield02:12:33

I don't use any of the reload middleware etc. I just eval each form -- each defn -- as I edit it, so my running app has the updated functions.

placeboza06:12:16

I enjoyed Clojure for the Brave & True, which is available for free online.

cjmurphy08:12:45

How to generate all the possibilities for a 'combination' lock with three positions, where each can be from set 0->9 inclusive, perhaps using clojure.math.combinatorics? I look to do it with the permutations function, but unfortunately it only takes one argument.

New To Clojure08:12:36

(for [x (range 10) y (range 10) z (range 10)
         :let [value (str x y z)]]
 value)

New To Clojure08:12:10

@bravilogy > I wish there were more resources for clojure Also there are excercises 4Clojure A resource to help fledgling clojurians learn the language through interactive problems. Clojure Katas A set of problems to help you get your hands dirty with Clojure. Wonderland Clojure Katas Clojure Katas inspired by Alice in Wonderland. Parens of the Dead A screencast series of zombie-themed games written with Clojure and ClojureScript. Clojure Koans (online) Exercises meant to initiate you to the mysteries of the Clojure language.

cjmurphy08:12:00

Thanks yes for would be good. But I still would like to see another way, as I think it is a 'permutations' problem.

cjmurphy08:12:15

There's also 'Advent of Code'. You get too see how others tackle the problems. It just started (beginning Dec).

New To Clojure08:12:07

@cjmurphy (combo/selections (range 10) 3)

cjmurphy08:12:09

Thanks @ghsgd2, that works perfectly.

Guilhem Brouat 10:12:19

Hey there! I am pretty sure I've already seen it, but I can't remember: how to get from

'({:id 1 :text "Buy bread"} {:id 2 :text "Pay taxes"})
to
{
  1 {:id 1 :text "Buy bread"} 
  2 {:id 2 :text "Pay taxes"}}
}

danm10:12:57

(reduce #(assoc %1 (:id %2) %2) {} '({:id 1 :text "Buy bread"} {:id 2 :text "Pay taxes"})) ?

cjmurphy10:12:40

(->> (map (fn [{:keys [id] :as val}]
             [id val])
          m)
     (into {}))

sundarj10:12:52

(group-by :id coll)

danm10:12:24

@sundarj That ends up with

{
  1 [{:id 1, :text "Buy bread"}]
  2 [{:id 2, :text "Pay taxes"}]
}
so might need a bit of further fettling to remove the lists

sundarj10:12:40

ah, good point

Guilhem Brouat 10:12:07

the solution with reduce works perfectly ! thanks all

danm10:12:53

It might not be the best solution mind you 😉 I do tend to use reduce sometimes when something simpler would do

cjmurphy10:12:57

I forget how to format 😜

danm10:12:17

Damn, how do you do a literal syntax quote character?

New To Clojure13:12:42

What is the preferred way of using Either monad in Clojure? I. e. is {:left nil :right value} returned from functions a good code style?

manutter5113:12:24

There was just a discussion about that not long ago. I mentioned the some-> macro as a way to do “Either”-ish programming, but there’s also using reduce with reduced, as other people mentioned.

manutter5113:12:58

Clojure isn’t strongly typed, so you don’t need a formal result type like {:left nil :right value}, you just write your code to handle failures appropriately.

New To Clojure13:12:06

@manutter51 some-> is nice but you can't find out what error happened

> (some-> {:a 1} :b inc)
nil
reduce with reduced code could be tricky to read.

New To Clojure13:12:04

btw I can't read that discussion because workspace has passed the free plan's message limit

manutter5113:12:43

Yeah, I was trying to summarize as best I could remember, I figured it would have scrolled out of view.

manutter5113:12:07

One other approach I played with was Elixir/Erlang style results with argument passing. I think I saw a Clojure Conj talk about Union Types that was a similar approach

manutter5113:12:34

so basically return a vector with a status and data, kinda tuple-ish

manutter5113:12:18

[:ok some-data] or [:error :some-error-code "Some Error Message"]

manutter5113:12:41

then use destructuring to split them out.

New To Clojure13:12:04

@manutter51 Thank you a lot! Tuple-ish seems to be a way to go.

manutter5113:12:44

You might want to check out https://github.com/manutter51/beanbag, that’s some old code I wrote playing around with this idea.

New To Clojure13:12:06

@manutter51 I wish it was part of Clojure core, but useful as is too. Thanks!

New To Clojure13:12:22

Somebody, please review two Clojure functions which use reduce. Is it a write-only code (large reduce's are hard to read) or a usual Clojure code?

joshkh14:12:37

not that it answers your question, but might a regex be faster?

New To Clojure14:12:43

@joshkh Regex with lookahead would be faster to code but I wanted to stick to pure Clojure (I'm learning the language)

joshkh14:12:44

fair enough!

rauh14:12:42

@ghsgd2 Does the above help?

New To Clojure14:12:31

@rauh that helps, thank you! Than I could map on partition-by results.

New To Clojure14:12:09

But in some cases using reduce will be inevitable.

joshkh14:12:39

i don't know much about transducers, but could you compose (map count) and (partition-by identity) to do the partitioning and counting in one go?

rauh14:12:34

There are many ways to go on after you have the partition-by result. Also with transducers. Next I'd probably map a (juxt count first) over it. See what that gives you

New To Clojure14:12:37

Amazing! What about decoding function (2nd)? I'll read about <https://eli.thegreenplace.net/2017/reducers-transducers-and-coreasync-in-clojure/|transducers> in the mean time.

rauh14:12:01

@ghsgd2 Btw, the transducer example is definitely advanced. A more beginner friendly version which is easy to understand is probably:

(->> "aabbbcddef"
     (partition-by identity)
     (map (juxt count first))
     (map (fn [[count char]] (if (< 1 count) (str count char) char)))
     (apply str))

rauh14:12:54

The reverse is very similar, just use a slightly different partition-by function, after that you probably want to use the repeat function and then you're done.

joshkh14:12:47

@ghsgd2 and if you did want to go in the regex direction this example might help. i wrote a little function to parse a string using a set of separators while ignore them if they appear within quotes:

(defn parse-identifiers
  [s]
  (let [matcher (re-matcher (re-pattern "[^(\\s|,;)\"']+|\"([^\"]*)\"|'([^']*)'") s)]
    (->> matcher
         (partial re-find)
         repeatedly
         (take-while some?)
         (map (partial (comp last (partial take-while some?)))))))

joshkh14:12:29

it took a moment for me to understand that re-matcher is stateful. just something to keep in mind in case you come across it. 🙂

New To Clojure15:12:04

@joshkh Thanks, will use it occasionally. @rauh Wrote decoding function.

(->> "10ABZ3CD4E"
     (partition-by #(Character/isDigit %))
     (map #(if (Character/isDigit (first %))
             (Integer. (apply str %))
             [(first %) (for [z (rest %)] [1 z])]))
     (flatten)
     (partition-all 2)
     (mapcat #(apply repeat %))
     (apply str)))

joshkh18:12:37

does anyone use yogthos/config to bake configuration into their uberjars?

joshkh18:12:32

lein with-profile dev run and lein with-profile dev run both pick up the respective config\[env]\config.edn files, however i don't get any configuration baked in when i run lein with-profile dev uberjar or lein with-profile prod uberjar

noisesmith18:12:43

@joshkh just speculating for a moment - I would expect a tool yogthos makes to produce a jar that is usable in both staging and prod, so that if it passes the staging qa tests the identical jar is also ran in prod

noisesmith18:12:53

that contraindicates baking config into the jar at build time

New To Clojure18:12:09

btw baking configuration into jars is against common DevOps principles

noisesmith18:12:20

I would expect using a tool like env to pull in the right config - or at least the name of the right config file

noisesmith18:12:08

oh wait! - it looks like yogthos/config really does want to be used that way, mea culpa

noisesmith18:12:12

that’s a weird decision IMHO

joshkh18:12:14

ha, slightly agreed. creating a {:profiles {:uberjar {:resource-paths ["config/prod"]}} fixed my problem but then i can't build separate dev and prod uberjars

noisesmith18:12:41

@joshkh btw that’s now how you add profiles

noisesmith18:12:53

lein with-profile foo bar replaces the bar config with foo config

noisesmith18:12:02

you probably want lein with-profile +foo bar

noisesmith18:12:08

which merges the configs

joshkh18:12:20

i forgot how profile stacking goes but lein with-profile uberjar +dev and lein with-profile uberjar +prod would be nice

joshkh18:12:26

yeah, my bad 🙂

noisesmith18:12:19

if yogthos/config works at all, (I would assume it would!) adding +dev or +prod should sort your issue - but also consider using config management that allows using one jar in multiple environments

joshkh18:12:24

aye, thanks for the advice. i usually manage the environments on a heroku / dokku platform with environment variables (which i think will override the baked in configuration file)

joshkh18:12:33

i like having "settings" configurations built in with the option of being overridden and excluding secrets and tokens.

joshkh18:12:11

(@noisesmith and thanks, lein with-profile +prod uberjar did the trick)

noisesmith18:12:25

:thumbsup: - I thought it would

joshkh18:12:43

speaking of config and environments, do you have any experience scaling up clojure apps on aws?

noisesmith19:12:43

our ops guy sets up an nginx reverse proxy, and has chef rules to spin up the app by downloading an uberjar from s3 and running it

noisesmith19:12:51

(along with setting env vars for config)

noisesmith19:12:07

and he uses the chef UI to control servers

joshkh19:12:33

uberjar to s3, interesting

noisesmith19:12:57

that way we can easily use any build - eg. quick rollback when something goes sour

noisesmith19:12:06

or even mixed deploys as needed

joshkh19:12:53

is there any containerisation happening? i'm about to scale up a big project and was thinking of using ec2 and docker images containing my uberjars

noisesmith19:12:14

we don’t need containers because we have no deps beside the vm itself and the uberjar

joshkh19:12:14

all of our components communicate via JWT which means all the components have to know the same secret. managing and rotating environment variables across multiple containers keeps me up at night. thanks for the info, i'll look into chef UI.

noisesmith19:12:05

chef might not be the thing we would use if we were starting from scratch - it was grandfathered in from ages ago and got used because otherp arts of the system used it and it was what our ops guy knew

noisesmith19:12:24

but if it does what you want that’s great 😄

noisesmith19:12:59

for example elastic beanstalk is very easy to use and more streamlined if all you are doing is putting up jars

New To Clojure19:12:03

There's a guideline for Clojure to avoid having mutable state. However, Clojure core doesn't avoid it when it's convenient (see partition-by implementation, for example). Does that mean that we should do the same? I'm thinking about using volatile! to speed up coding (for example, at times I need to have reducefunction which takes previous element value into account).

noisesmith19:12:24

@ghsgd2 best practice is to make things immutably first, then profile when it’s too slow, and then consider volatiles or even directly mutable data as needed in the bottlenecks

noisesmith19:12:36

I wouldn’t just throw around optimizations before seeing a need and measuring

joshkh19:12:23

@ghsgd2 i think it's okay to work with mutable objects if you're careful. i have an in-browser file system that entirely makes use of (derive) to define file/folder hierarchy because it's much faster than the alternative. then again the folks in #clojure said i was crazy. 😉

noisesmith19:12:22

@joshkh my main concern is that doing that first, before an immutable version, could be wasting your time (if the immutable version is fast enough, or if something else would be more likely to improve perf)

noisesmith19:12:36

it’s not crazy to want things to perform well, at all

joshkh19:12:53

yes, totally agreed, especially if (like me) you come from an OO background and didn't start with best practices for optimising the immutable parts of clojure

joshkh19:12:54

i was so guilty of rebinding the same thing a million different ways in let statements (let [a 1 b 2 a (+ a b)]). that was a hard habit to break.

New To Clojure22:12:42

Is there a reason why (into {} '(2 1)) throws IllegalArgumentException Don't know how to create ISeq from: java.lang.Long?

manutter5122:12:09

I think it’s probably expecting the last argument to be a map, or at least a list of tuples

New To Clojure22:12:48

> list of tuples

> (into {} '((:key 1)))
 ClassCastException clojure.lang.Keyword cannot be cast to java.util.Map$Entry

manutter5122:12:49

No, that’s not getting it for me either

manutter5122:12:03

I even tried quoting the inner list, but no good

manutter5122:12:32

It works with vectors, but not seqs

manutter5122:12:45

(into {} [[:foo 1]])
=> {:foo 1}

New To Clojure22:12:32

@manutter51

(into {} ['(:key 1)])
ClassCastException clojure.lang.Keyword cannot be cast to java.util.Map$Entry
(into {} [[:foo 1]]) works indeed. Thank you!

manutter5122:12:35

(into {} '([:foo 1]))
=> {:foo 1}
also works

seancorfield22:12:34

A two-element vector can often stand in for a MapEntry, but not a two-element list.

admay22:12:18

Is there a repl function to see more about a specific type similar to (doc function-name)?

admay22:12:26

I’m curious as to why lists can’t be cast to map entries. I’m guessing it has something to do with performance

seancorfield23:12:23

There's specific code in some clojure.core functions that accept vectors with two elements where a MapEntry would otherwise be expected.

Alex Miller (Clojure team)23:12:32

Map entries require direct access to the key and value slot. Lists would require traversing the key to get to the value

Adam Faraj23:12:38

Hello Everyone. Are there any good resources to learn Clojure quickly and efficiently?

Drew Verlee23:12:51

It depends a bit on what your background is.

Adam Faraj02:12:39

@drewverlee front end development

seancorfield23:12:39

(based on what you posted in #clojure another resource to look at is the Clojure Koans https://github.com/functional-koans/clojure-koans )

seancorfield23:12:52

That's fun and interactive.