Fork me on GitHub
#beginners
<
2017-11-23
>
hawari04:11:32

Hello everyone, I've just recently started with clojure. Coming from C-like programming languages (I have some experiences in Java and Go), I've gotten used to do some kind of early return function like:

if userNotFound {
    return ErrorNotFound
}

doSomethingElse()

if somethingElseHappened {
    return UnexpectedError
}

return nil
I mainly used it when writing web services to determine what kind of response would I want to return to the caller. For example, if the return value is some kind of error or exceptions, I would return the status code appropriate to the type of error, usually the case of 4xx vs 500. When using clojure I understand I can't really write it like that anymore so the most that I can came with is something like this:
(defn signup
  [credential]
  (let [user (find-by-email (:email credential))]
    (or
     (invalid-registration? credential)
     (user-already-exists? user)
     (register credential))))
I can't help but feel that there should be more idiomatic way to write this kind of functionality. Is there another way to do this kind of function or is this acceptable?

seancorfield05:11:35

@hawari.rahman17 There are various ways to handle this sort of thing. If you have predicates that return an error code or nil, using or is fine, but it's probably more idiomatic to use cond:

(defn signup
  [credential]
  (let [user (find-by-email (:email credential))]
    (cond (invalid-registration? credential)
          :invalid-registration
          (user-already-exists? user)
          :user-exists
          :else
          (register credential))))
and then your predicates would just return true/false.

seancorfield05:11:56

This would turn the predicates into "pure" predicates (`?` usually means Boolean -- just true or false), and decouples the error codes from the predicates themselves.

hawari06:11:46

Ah I see, seems like I have to rewrite it then. As of now my invalid-registration? or other "validation" function returns a vector as the truthy value, containing the error type and the message itself:

(defn invalid-registration?
  [credential]
  (if (invalid-credential? credential)
    [:bad-request (compose-error (credential-errors credential))]
    false))
What do you suggest if I had to return such value @seancorfield?

hawari06:11:43

I was kinda hoping of something akin of a flow, where if there's something happened on any part of the flow, don't proceed to another step.

seancorfield06:11:15

If something is a predicate -- with ? -- then it should just return true or false. The error code/message is a control flow function and belongs in the "controller" logic, not the predicate. Otherwise you're conflating a test with how it should be exposed to end users.

seancorfield06:11:30

One of the "themes" of Clojure is keeping things separate and uncoupled -- uncomplected. @hawari.rahman17

hawari06:11:18

Alright, I think I get the idea now, do you mean something like:

(defn signup
 [credential]
 (let [user (find-by-email (:email credential))]
   (cond (invalid-registration? credential)
         (do-something-if-invalid)
         (user-already-exists? user)
         (do-something-if-already-exist)
         :else
         (register credential))))

seancorfield06:11:10

That separates the decisions from the actions, which is a good way to go.

hawari06:11:15

Thanks @seancorfield, I think I quite understand now. Such a concise and clear explanation.

seancorfield06:11:51

Cool. And "Welcome to Clojure!" 🙂

hawari06:11:46

Thank you, looking forward to work with this awesome language 😁

seancorfield06:11:18

@hawari.rahman17 I started with Clojure in 2010 and we've had it in production since 2011. Very happy with that decision!

hawari06:11:23

Can't think of anyone better to learn from then 😁

Empperi09:11:57

maybe Rich Hickey? 🙂

lum11:11:19

Hi, when I add the period "." to the last line of the books->string function I get the print of LazySeq, an don't understand why. First without the ".":

(defn books->string [books]
  (cond
    (= 0 (count books)) (str "No books.")
    (= 1 (count books)) (str "1 book. " (apply book->string books) ".")
    :else (let [each-book
                (fn [book] (book->string book))]
            (apply str (count books) " books. " (interpose ". " (map each-book books) ) ))))

(books->string [little-schemer, cities, wild-seed])
"3 books. The Little Schemer, written by Daniel Friedman (1944 - ), Matthias Felleisen. The City and the City, written by China Miéville (1972 - ). Wild Seed, written by Octavia E. Butler (1947 - 2006)"
but I need the "." at the end of the text. Thought that this would do it as the last line of the defn:
(apply str (count books) " books. " (interpose ". " (map each-book books) ) "."))))
but returns "3 books. clojure.lang.LazySeq@3c7e9f68.". Why this since apply str should (in my view) apply to all arguments?

noisesmith11:11:44

@lum apply only "unwraps" the last arg

noisesmith11:11:17

so you get the str of a lazy-seq (totally useless) then each element of "." - a single char

noisesmith11:11:02

you can fix this by having an extra call to str wrapping the whole thing, or by using (concat (map each-book books) [""])

noisesmith11:11:06

or using (interleave (map each-book books) (repeat "."))

noisesmith11:11:14

(instead of interpose)

lum11:11:13

thank you very much @noisesmith. Didn't know that it'd unwrap only the lat argument. I found another way, after knowing that: (str (count books) " books. " (apply str(interpose ". " (map each-book books) )) "." )

noisesmith11:11:19

right, my first option "extra call to str wrapping the whole thing"

lum12:11:36

ah, ok, I understood a new str would come first. Sorry, I'm not native english speaker so don't get right some meanings.

noisesmith12:11:07

@lum here's a comparison of interpose and interleave

+user=> (interpose "." [:a :b :c])
(:a "." :b "." :c)
+user=> (interleave [:a :b :c] (repeat "."))
(:a "." :b "." :c ".")

noisesmith12:11:51

now that I think about it, clojure uses some very obscure english words 😄

lum12:11:13

I'm doing this to the clojure course, and didn't "learn" interleave yet, so can't apply, but it's good to know. 🙂

noisesmith12:11:10

another way to get that would be (mapcat #(vector % ".") [:a :b :c])

lum12:11:46

this is another level...

vuuvi15:11:22

could someone please explain why the constantly function is helpful?

noisesmith15:11:31

@alexkeyes consider that you have some conditional deciding what function gets passed to a higher order function

noisesmith15:11:49

sometimes (constantly true) is the most straightforward way to describe what you want

solf15:11:25

Is there a better way of chaining maps than : (->> (range 36) (map index-to-position) (map cells-around-position) (map count)) ?

sundarj15:11:33

@dromar56 you could do (->> (range 36) (map (comp count cells-around-position index-to-position)))

vuuvi15:11:10

@noisesmith thanks for the concise explanation!

solf15:11:50

Interesting, thanks @sundarj. I still have some trouble with comp and its (seemingly) reversal of order

sundarj15:11:56

@dromar56 it follows the order you would write the functions manually: ((comp f g h) x) => (f (g (h x)))

noisesmith15:11:58

@dromar56 you can use transducers with a small change to your syntax (into [] (comp (map index-to-position) (map cells-around-position) (map count)) (range 36))

solf15:11:44

I still haven't read about transducers yet, but I'll keep that in mind 🙂

noisesmith15:11:22

the funky thing is that transducers compose functions rather than composing values from the functions, so the order goes backward to normal comp

joshkh15:11:27

i don't think this is a clojure specific question but it comes up a lot when i'm writing clojure. 🙂 i have a clojar dependency with a CSS file inside the jar. how can i reference that css file within my parent clojure project / scss build tool?

vinai19:11:29

Can someone recommend a resource on how to deal with input validation in a clean way without exceptions? Are Monads the only option? Or is there another way?

sova-soars-the-sora19:11:46

@vinai input validation in... the browser? on the command-line? what are you getting into?

vinai21:11:50

@sova Server side, input via an api request.

vinai21:11:19

Sorry for the late reply, intermittent network outages.

sova-soars-the-sora22:11:48

@vinai how many different inputs are you working with? if you need to make hundreds over the lifetime of the app it may be worthwhile to invest some time in solving the general case. my treatment of this on the web side is with atoms that hold the values of the inputs and are validated on submission action... since it's an api request you can't really limit the input fields as you would an input box, but you can definitely deconstruct the input via compojure in your router (server.clj) and make a handful of custom methods that will "validate" inputs, returning some sort of general case "hey that dint work my man" and perhaps the list of specific inaccuracies. so you can haeve a method to validate dates, to validate integers, phone numbers, whatever.. but i think you may need a custom validation method for each kind of input you're working with. are they really abstract bodies of data?

noisesmith22:11:15

in terms of libraries, plumatic/schema has code designed for validating client data https://github.com/plumatic/schema

noisesmith22:11:50

@sova why use an atom?

sova-soars-the-sora23:11:37

@noisesmith my mind is in web dev mode 😄 as far as i am aware, the only convenient way to persist values in something like client.cljs is with an atom, and you can then use the atom references when you wish to submit / send stuff over the wire.

13tales23:11:29

Hey there. Anyone got time for a newbie question regarding dependencies? I’m trying to use the clojure/math.numeric-tower library, and running into Class not found issues 😞

13tales23:11:06

I’m following the example given in the library documentation.

13tales23:11:23

I’ve added the dependency to project.clj

13tales23:11:06

I’ve run lein deps, and Leiningen indicated that it’s downloaded the library.

13tales23:11:08

I’m attempting to import it with (require [clojure.math.numeric-tower :as math]), with no success 😕