Fork me on GitHub
#beginners
<
2017-11-03
>
petr.mensik08:11:43

Hello guys, just a quick question about case, I tried to make this code working but I haven't succeed

(let [contact-status (case (lib-int/parse-int-or-nil status)
                         (:won all-statuses) (:current-client contacts/all-statuses)
                         (:lost-project all-statuses) (:lost-opportunity contacts/all-statuses)
                         (:lost-invoice all-statuses) (:ex-client contacts/all-statuses)
                         nil)]
Each test condition evaluates to a different number however I am getting Duplicate test constants. Does anybody know why is that? I've read documentation and I know that constants must be compile-time literals but I am not sure what exactly it means...is it that all conditions are actually a "quoted" list so I need to use cond?

petr.mensik09:11:23

Solved it with (condp = status), is there a better solution?

schmee09:11:15

do you expect the list of cases to grow, or will it “always” be those three?

petr.mensik10:11:23

@schmee It might change but it will moreless stay the same

schmee10:11:36

ok, then condp is probably good 🙂

schmee10:11:07

and core.match if you want full-fledged pattern matching: https://github.com/clojure/core.match

petr.mensik10:11:22

Ok, thanks - btw I looked at core.match but I didn't want to introduce new dependency just for matching in one functin 🙂

petr.mensik10:11:29

but nevertheless it looks nice

jumar10:11:31

@petr.mensik compile-time means that you have to use constatnt expressions (numbers, strings, symbols, keywords, etc.) anything that has unknown value during compile time is not acceptable

petr.mensik10:11:17

@jumar yep, I just wanted to make sure I really got it 🙂

jumar10:11:10

if you want to dig deeper check this screencast: https://www.youtube.com/watch?v=_f3WeoDSN-I

noisesmith16:11:11

@petr.mensik case is weird, for example

user=> (case 2 (1 2) :OK)
:OK
- it uses () in a way pretty much nothing else does in clojure, as a grouping construct rather than a function call syntax

noisesmith16:11:34

in fact, it’s one of the only things I would change about the clojure special forms if I were re-implementing the language, because it’s such a weird special case

rcustodio18:11:07

is there anyway to improve the error message from clojure.spec?

(spec/def ::document (spec/keys :req-un [::number ::type]))
example (spec/assert ::document x) be “the document field is missing” or “the number field is missing in document”

ghadi18:11:52

@rcustodio there's a thing called explain-data which gives you back a datastructure that a tool might use to make really nice output

rcustodio18:11:19

spec/explain-data ?

rcustodio18:11:38

Thanks, I will check it

ghadi18:11:42

Here is an example of a tool that use it to make stuff nice

ghadi18:11:04

the default explain is but one possible consumer of explain-data

ghadi18:11:51

There is an example of the output ^

rcustodio18:11:03

Okey, thanks, and do you know if there’s someway of use db validation? (like field x exists)

ghadi18:11:13

with spec?

rcustodio18:11:42

yes, with spec

rcustodio18:11:00

I tried to find it, but no success

ghadi18:11:14

you can use stuff to validate things going into the db or coming from the db. However, defined spec should be side-effect free. Like don't write a spec that checks for the existence of something in the database, that would be turrible

rcustodio18:11:38

Hmmm, I see, so that really should be a different place

ghadi18:11:57

Yup, different layer entirely

rcustodio18:11:17

like (do (spec/assert ::document x) (assert (document-exists? x)))

ghadi18:11:36

for example yeah

ghadi18:11:52

depending on specs (esp s/or s/alt) your specs might be called more than once

rcustodio18:11:22

I see, thanks!!

rcustodio19:11:44

is there anyway to convert (clojure.core/fn [%] (clojure.core/contains? % :type)) to string

rcustodio19:11:56

form to string would be, i guess

Alex Miller (Clojure team)19:11:50

(str '(clojure.core/fn [%] (clojure.core/contains? % :type))) :)

derpocious19:11:25

hey all, can I ask a beginner question

derpocious19:11:36

I have some code where I have a string and an empty vector. I want to basically map over the string and put each character into the vector. In this code I create a string and an empty vector. Then I loop over the string with "doseq" and save the character into my vecotor. However, when I print the vector at the end of the program, it is always empty! For example, this code: (def stringo "HelloO") (def saves []) (doseq [x stringo] (println x) (conj saves x)) (println saves) prints this for me: H e l l o O [] And similar code using "let" returns the same thing: (let [stringo "HelloO"] (let [saves []] (doseq [x stringo] (println x) (conj saves x)) (println saves))) So my question is basically 1) Why does it print an empty vector instead of a vector containing all the characters? and 2) How should I decide whether to use def or let in situations like this? Thanks!

noisesmith19:11:57

@derpocious conj does not change the vector

noisesmith19:11:07

it returns a new vector, with your addition applied

noisesmith19:11:44

you need to use that vector, with a case like yours you could use reduce to add the new item at each step

chris19:11:59

the difference between let and def is one of scope

noisesmith19:11:07

unless this is just an exercise, you can use into, which is already conj inside reduce plus some other features

dpsutton19:11:13

strings are collections in clojure so you can "pour" it into another collection with (into [] "hello world") or just look at it sequentially with (seq "hello world")

chris19:11:38

or (vec "hello")

noisesmith19:11:04

but the version using conj is (reduce conj [] saves)

noisesmith19:11:11

for reference

dpsutton19:11:12

always forget about vec. thanks

dpsutton19:11:18

@derpocious do you understand why it didn't change your var saves? Want to make sure it was clear what was going on

derpocious19:11:16

hmm so there is no way to reset the saves vector?

noisesmith19:11:24

it’s not mutable

dpsutton19:11:41

usually that means you want to think about it differently. especially when learning clojure, you want to get away from thinking about mutating

manutter5119:11:31

@derpocious In non-Clojure languages, you normally copy a string into an array using the exact style you did: define a couple variables, then go through a loop modifying those variables. Clojure and other functional languages don’t do it that way, and in fact they don’t even think that way. In Clojure, you pass values into functions that give you back new values. So in Clojure you’d say, “Here is an immutable string value, please give me back the corresponding vector (array) value.”

dpsutton19:11:40

and that probably sounds infuriating, but what question are you answering with this bit of code. we can design a function that answers that question without the mechanics of changing a variable a bunch

derpocious19:11:42

Hmm ok. That sort of makes sense.

derpocious19:11:11

I want to make a function that takes a string and returns a vector of the characters that occurred more than once.

manutter5119:11:43

That sounds fun 🙂

dpsutton19:11:45

ah ok. that's perfect.

dpsutton19:11:05

so there's several ways to do it. some that are kinda cheating since the language itself offers tools very close to that

dpsutton19:11:16

but let's do one that's a little more manual to learn how to keep and accumulate state

dpsutton19:11:43

as we're going down this string, we need all of the character's we've seen so far. and if we haven't seen the character so far we add it to the set, right?

dpsutton19:11:05

and the way we consider every letter in this string is with reduce

derpocious19:11:02

wait, are you using a separate set to keep track of ones youve seen and the return set?

derpocious19:11:05

or just one?

dpsutton19:11:14

do we need two?

dpsutton19:11:29

is there a difference between what we've seen so far and the distinct letters?

derpocious19:11:02

the question isn't just to return distinct letters, return only the letters that occur more than once.

dpsutton19:11:43

ah my mistake. ok. so we need two sets then

dpsutton19:11:19

so imagine we have a function that takes two arguments. the first argument has the two sets in it. seen and seen again. and the second argument is the character we're considering

dpsutton19:11:51

and the logic is, if the character is in the seen set, add it to the seen-again set

derpocious19:11:31

ok, and if it's not in seen, add it to seen, right

dpsutton19:11:56

and we'll use clojure sets since those are convenient for us. they're one of the fundamental data types and they'll make it easy to not worry about deduping

derpocious20:11:28

but I should use reduce for this?

dpsutton20:11:22

what else are you thinking. i'm sure there are many ways to skin this cat

derpocious20:11:57

hmm idk I like doseq because it seems like just a normal loop over the collection

manutter5120:11:08

I’d recommend loop/recur

manutter5120:11:23

Just because it’s closer to what you’re likely familiar with

noisesmith20:11:31

if you are consuming a collection reduce is always better than loop/recur, it’s clearer, more concise, and performs better

manutter5120:11:04

reduce is the better way to do it programmatically, but loop/recur may be easier to understand in the early stages of learning Clojure

noisesmith20:11:10

doseq is only useful for side effects, you can’t collect values from it

derpocious20:11:53

but can reduce take a collection and return a collection?

dpsutton20:11:56

the reason i like reduce is because reduce means reduce a collection down to a value. so when you want a set of the duplicated letters i'm gonna do that

manutter5120:11:21

reduce can take a collection and return pretty much whatever you want to get 😉

dpsutton20:11:46

and what makes reduce stand out to me in this case is that reduce uses a function who's signature is two arguments: the result from previous calls of the function and the next element in the collection. So it's "the result of what we've considered so far" and "what to consider now"

dpsutton20:11:39

and the body of the function is how to merge the current element into the result of what we've considered so far

dpsutton20:11:54

@derpocious does that give you enough to try to write the function on your own?

derpocious20:11:52

Thanks a lot guys 👍

dpsutton20:11:01

do you know how to make a set?

dpsutton20:11:34

#{}. clojure makes data types really easy to make. and you can make a vector with []. So if you want a vector with two sets in it you can just write [ #{} #{} ]

sundarj20:11:20

alternatively (hash-set 1 1 2 3) and (vector 1 1 2 3)

derpocious20:11:55

cool. So #{} is a hash set? I remember learning there is also sorted set. Are there other kinds of sets?

dpsutton20:11:16

there is a sorted set but you need to load the library that provides it. so don't worry about that for right now. there are lots of custom data types out there but the vast, vast majority of clojure code is written with the few data structures: hashman {}, hashset #{}, vector [] and sequences

sundarj20:11:05

sorted-set and sorted-set-by are in clojure.core

dpsutton20:11:02

ah apologies. i think i was thinking of priority map or something

athomasoriginal21:11:35

I am having trouble breaking into the Functional Programmer mind set for the following:

function eventHandler (e) {
    let x = e.target.offsetHeight
    let y =  e.target.offsetWidth

   if (condition) {
       x += 10 //update value
       y += 15 // update value
   }

  doSomethingWithSideEffects(x, y)
}
The above has local mutating state. This is not cool in Clojure, but how would clojure solve the above problem? Its not reducing over anything, so can't use that, and no need for recurr. Would the best solution be to create a function thats used in eventHandler which would return either the original x and y or a modified x and y?

dpsutton21:11:25

x and y seem to be the same thing here to me unless i'm reading incorrectly?

athomasoriginal21:11:58

sorry, just updated. Both x and y will likely be different

dpsutton21:11:08

(defn get-dimensions [e]
  (let [height (.. e target offsetHeight)
        width  (.. e target offsetWidth)])
  (if condition
    [(+ 10 height) (+ 15 width)]
    [height width]))


(let [[height width] (get-dimensions e)]
  (compute (stuff x y)))

dpsutton21:11:11

this is what i would do

dpsutton21:11:47

the bottom let could be another defn or whatever you need

athomasoriginal21:11:17

That feels along the same lines of what I wanted to do. Good example and thank you!

aconanlai21:11:58

hi everyone!

aconanlai21:11:08

new to clojure 🙂

aconanlai21:11:49

i'm making a CRUD app as my first project to familiarize myself with both backend and CLJS/reagent - i'm having a silly problem that's probably something simple i'm missing, i was wondering if someone could help?

aconanlai21:11:30

when the server receives a POST request to a certain endpoint, if there is no table with the same name as the id param, we create it

aconanlai21:11:00

but it seems my when-not is not working... this is an abridged version of my server file:

aconanlai21:11:37

the create-table function... it tries to create the table even when it already exists

dpsutton21:11:51

do you know how to get a repl up and running? Can you call db-exists? on a table you know already exists and see what it is returning?

aconanlai21:11:13

yes, it returns true

aconanlai21:11:37

clj-youtube-server.core=> (db-exists? "comments") true

dpsutton21:11:24

and you see that when you post to /video/

dpsutton21:11:57

because i don't see that check on the post handler

aconanlai21:11:11

the check is in the create-table function

dpsutton22:11:06

put a println statement there in the when-not function. and let's see what value name has there. i might imagine that there's a type mismatch going on. perhaps the id is coming in in a slightly different format than you expect

dpsutton22:11:32

or a debugger if you know how to hook one up in cursive or cider

aconanlai22:11:12

im using VS code with parinfer and the repl plugin for now... not ideal but i figured i would switch to cursive/light table after learning more

dpsutton22:11:40

don't worry about that. some people really like the VS code setup. do what works for you.

aconanlai22:11:20

where should i put the println? within create-table? or within db-exists?

dpsutton22:11:32

right after the when-not i think

dpsutton22:11:43

see exactly what it thinks doesn't exist and then check it on your own

aconanlai22:11:42

i can't seem to get it to print...

aconanlai22:11:45

(defn create-table
  [name]
  (do
    (println "hua"
     (when-not (db-exists? name)
           (sql/db-do-commands spec
            (sql/create-table-ddl name [[:comment "varchar(120)"] [:time :int]]))))))

dpsutton22:11:22

the println seems like it extends to the when-not expression. (println "hua")

dpsutton22:11:28

notice your close brace isn't there

dpsutton22:11:52

and did you reevaluate the defn form?

aconanlai22:11:11

how do i re-evaluate the form?

dpsutton22:11:29

i don't know in VS code. but there should be a notion of evaluating a form

aconanlai22:11:49

do you do it through nREPL?

dpsutton22:11:17

yes your editor will evaluate the code through the nrepl backend

dpsutton22:11:25

should just be a key command in your editor

aconanlai22:11:42

so (println (db-exists? name)) evaluates to false

aconanlai22:11:08

even though i know the table name exists when i run a SQL query to check it

aconanlai22:11:16

so i guess i need to cast the string to something else?

dpsutton22:11:22

try (println "name is:" name) and make sure it's actually what you think it is

aconanlai22:11:34

i guess postgres converts all CREATE TABLE requests to lowercase

aconanlai22:11:47

or rather tables can only consist of lowercase letters

aconanlai22:11:35

so it's not really a clojure problem at all haha

dpsutton22:11:32

that's crazy. well this is a way you can get some introspection into your apps. using the repl and such

dpsutton22:11:35

best of luck!

aconanlai22:11:24

thank you so much for the help! i really appreciate it 🙂

aconanlai22:11:34

im gonna set up a good workflow ASAP

dpsutton22:11:43

for sure. enjoy clojure!

seancorfield22:11:53

PostgreSQL only respects case if you wrap entity names in double quotes.

seancorfield22:11:47

BTW @U621RMVAN This opens you up to a SQL injection attack:

(sql/query spec
                 [(str "select count(*) from information_schema.tables where table_name='" name "'")])
because you are constructing the SQL via string concatenation. You should use query parameters like this:
(sql/query spec
                 ["select count(*) from information_schema.tables where table_name = ?" name])
That is much safer.

seancorfield22:11:56

p.s. Happy to answer any questions you may have about clojure.java.jdbc -- I'm the maintainer!

aconanlai22:11:58

wow thank you!

rcustodio23:11:00

#(str (:pred %1)) returns "clojure.lang.LazySeq@6c40fb0f" how to make it return the right lazyseq?

phronmophobic23:11:38

#(str (pr-str (:pred %1)))

phronmophobic23:11:13

#(pr-str (:pred %1))

rcustodio23:11:23

that helps a lot