Fork me on GitHub
#beginners
<
2019-11-20
>
parker00:11:12

So if I understand right, having all the source files in src/project-name/ prefixes their ns's (e.g. project-name.foo) and prevents grief if I end up bringing in a lib also named foo? And it sounds like you can nest additional subfolders to organize your stuff, and that will be reflected in the ns's as well? Neat.

bfabry00:11:42

"namespaces are one honking great idea"

bfabry00:11:29

although it's more that the namespace must reflect the folder layout rather than the namespace "will" reflect the folder layout. in your (ns project-name.foo declaration you have to specify the full and correct namespace as dictated by the folder nesting

parker00:11:09

Gotcha, thank you!

vinurs01:11:21

how can i make (+ 0.3 0.6) to 0.9?

vinurs01:11:46

and (+ 0.2 0.1) => 0.30

johnj01:11:39

quick answer is, use bigdecimal

johnj01:11:45

(+ 0.3M 0.6M)

andy.fingerhut01:11:29

Most floating point numbers (Java type float and double, based on IEEE 754 standard) are approximations. This section of the Clojure equality guide article might have some useful info: https://clojure.org/guides/equality#_floating_point_numbers_are_usually_approximations

ScArcher03:11:34

I'm connecting to a Socket with clojure and I'd like to read messages from the socket, then for each message I receive I'd like to call a handler function. I'd like the handler to happen in another thread, but ultimately when it returns a value, I'd like to queue that up to send as a response over the socket. Does anyone have a recommendation on how I should approach this? I've written the code to connect to the socket, send message, and receive messages so far. I'm not sure how to approach the rest.

ScArcher03:11:16

Should I setup separate threads for reading / writing to the socket, so that one can block on reading, the other can just wait for messages, then send them one at a time?

alexmiller03:11:11

A common pattern is to have one thread that accepts incoming messages then hands them off to a pool of threads to respond

ScArcher03:11:16

Looking at core.async now. Just unsure of what approach / library that would be appropriate here.

ScArcher03:11:15

In Java, I would probably create a fixed thread pool executor service and could handle message processing that way.

alexmiller03:11:25

I’m not sure core.async helps you a lot here, I would just do that

ScArcher03:11:12

Would this be the relevant reading material - https://clojure.org/about/concurrent_programming ?

dpsutton03:11:30

you can look at https://github.com/clojure/clojure/blob/master/src/clj/clojure/core/server.clj#L115-L117 to get an idea of how clojure's built-in socket server works

dpsutton03:11:49

its not quite what you're looking for but perhaps it can serve as a jumping off point

ScArcher03:11:08

Thanks, that's helpful! I'm trying to learn the "clojure" way of doing things and every time I fall back to Java I'm worried I'll implement my solution in a familiar way that prevents me from seeing the benefits of functional programming.

ScArcher03:11:26

I'm basically porting a working c# app to clojure in order to learn Clojure and hopefully provide an example to others I work with on the benefits of Clojure. (The REPL is the best part so far)

dpsutton03:11:55

if you internalize immutable values and write java flavored clojure i don't think you're in a bad spot to create lots of functional things. as you read code and run into tough spots from writing java flavored clojure you'll discover benefits of different ways. but don't let the perfect be the enemy of the good. and always ask questions here 🙂

ScArcher03:11:46

Thanks for the encouragement. So far the "socket" is the only part of the application that isn't "purely functional" so I already see other benefits.

ScArcher03:11:58

(`defmacro ^:private thread
  [^String name daemon & body]
  `(doto (Thread. (fn [] [email protected]) ~name)
    (.setDaemon ~daemon)
    (.start)))
So I was looking at the code and I saw (thread ) but I didn't know what that was, so then I saw the definition of a thread macro. Should I just use this as-is and not ask questions 🙂 or do I need to understand a bit about what this is really doing? It looks like it's creating a thread, setting the daemon flag, and executing what's the in body within the thread's run method and starting it up. Is that a valid take? I haven't written or read much on Macros so far.

dpsutton03:11:29

use triple backticks around the region instead of puting them on each line

ScArcher03:11:24

That's what I get for clicking the "code" button 🙂

dpsutton03:11:46

yeah. the real benefit to this is the obvious, it ensures that set-daemon is set. and it also enables a bit of sugar in that it takes a form and wraps that in a function rather than requiring the caller to do so. So this doesn't need to be a macro but makes the usage a bit nicer

👍 1
dpsutton03:11:19

if you see how its used, one of its arguments is the form (accept-connections conn ...). If this wasn't in a thunk then it would be evaluated on the calling thread rather than delayed and invoked on the newly created thread

ScArcher04:11:22

Is there an easy way to make a function synchronized so that only one thread can enter at a time?

ScArcher04:11:48

;; lock protects servers
(defonce ^:private lock (ReentrantLock.))
(defonce ^:private servers {})

(defmacro ^:private with-lock
  [lock-expr & body]
  `(let [lockee# ~(with-meta lock-expr {:tag 'java.util.concurrent.locks.ReentrantLock})]
     (.lock lockee#)
     (try
       [email protected]
       (finally
         (.unlock lockee#))))) 
I think I found the answer in the code.

noisesmith18:11:36

this looks like an attempt to do clojure.core/locking from scratch

noisesmith18:11:53

now I see this is in clojure.core.server, I wonder why one would use this instead of locking (or visa-versa...)

Noel Llevares04:11:44

Do I have to know Java before learning Clojure? I want to avoid Java if possible.

Noel Llevares04:11:48

For additional context, I’m comfortable with NodeJS/Typescript, Python, PHP, etc. I did some Java back in the day but didn’t enjoy it.

Rex04:11:34

Clojure uses a lot of underlying Java DS and libs, i reckon you might want to look into common Java DS?

Rex04:11:21

there might be time when you want to make use of Java as well like the code block above with ReentrantLock

seancorfield05:11:00

@noelmartin you can get a long way with Clojure without knowing Java. About the only skill you'll need is reading stack traces IMO

Noel Llevares05:11:50

Nice. I’m backend-oriented so I’d like to start learning Clojure by writing up some backend APIs. Any suggestions for Clojure equivalent of batteries-included web frameworks (e.g. Django, Rails, etc.) and microframeworks (e.g. Flask, Slim, Express, etc.)?

1
ScArcher05:11:19

@noelmartin I'd suggest checking out some of the frameworks referenced in "Web Development with Clojure" - https://pragprog.com/book/dswdcloj3/web-development-with-clojure-third-edition I think Luminus is the framework, but it uses several libraries.

👍 1
Noel Llevares05:11:10

Perfect. I prefer learning from books so this is great!

solf06:11:28

Any way of turning warn-on-reflection only for own source files, and not for dependencies? Or is this a bad idea?

em06:11:57

@noelmartin https://www.youtube.com/watch?v=0EX3UIl-Sd8 This was a great talk that goes in depth and clarifies a fairly standard and idiomatic way of constructing a web application, and is also filled with solid suggestions for further study. With the plethora of choices in Clojure web development, this was really grounding and helped me see not just what choices exist, but why they exist and how each is useful.

Noel Llevares06:11:55

Thanks. I'll watch this.

👍 1
solf10:11:53

Do you append ! for impure functions that read state in either atoms or external databases or should it be only for functions that modify state?

Shuai Lin10:11:58

My 2c: it's a sign for "caution, side effect", so for reading atoms/database I think it's not required.

jaihindhreddy11:11:47

Even clojure.core is not consistent here. - persistent! has bang because it is destructive on the transient argument. - alter and alter-var-root don't need it because they are within the epochal time model. - But swap! despite being in the epochal time model has a !. - run! has a bang because it's intended for side effects - print and related don't even though they're effectful. I can go on but, you get the point. I'll leave you with a snippet if you wanna look at all the stuff in clojure.core with a !:

(->> (ns-publics 'clojure.core)
     keys
     (map str)
     (filter #(re-find #"!" %)))

delaguardo11:11:00

In general side effect is anything that causing unpredicted behaviour. Reading from atom could return different results depending on the state of atom.

solf11:11:12

I'm leaning towards not putting a bang for database reading functions. Being too strict about putting a bang on side effect functions isn't helpful either, as even print has unpredicted behavior

jaihindhreddy12:11:18

The modern usage of the word behaviour is a bit vacuous. One precise definition of "pure function" is "referentially transparent", because a fn that prints something always might return the same output given the same input. But still won't be a pure function as per this definition. Should we name all fns that aren't known to be referentially transparent with a !? Maybe. Maybe not.

henrik14:11:24

There’s a bit of a distinction between “causes side-effect” and “is impure.” You could have a function that returns the same result predictably based on the same input, and causes all kinds of havoc on the side. You could have another function that returns unpredictable results (`rand-int`, for example), but doesn’t mutate anything in the environment. Reading the DB is impure, but presumably doesn’t side-effect. I tend to use bangs for mutations.

mloughlin15:11:29

Scheme has traditionally used ! to denote destructive mutations, and specifically not I/O

ScArcher16:11:40

I'm currently trying to read a line from an inputstream and call a function to handle that line. I need this in a loop until the line is nil. What's the best way to do this? It's like I need a let binding in a while condition or something.

dpsutton16:11:52

(doseq [line lines] (f line)) should work for you. you can also use loop if you want a bit more control over when to recur

ScArcher17:11:58

So where would my readline go? in place of "lines" ?

Lu17:11:21

(with-open [rdr ( file)]
             (reduce
              (fn [parsed line] your logic)
              ""
              (line-seq rdr)))

Lu17:11:57

if the line is nil call reduced on it to break the loop

ScArcher17:11:53

@U11BV7MTK I tried (doseq) but it treated the first returned string as a sequence of characters. Any idea what I did wrong?

ScArcher17:11:45

(doseq [line (.readLine reader)] (println line))

dpsutton17:11:17

that's on a single line. so it returns a string and that's a sequence of characters

👆 1
dpsutton17:11:24

check our line-seq

dpsutton17:11:31

(source line-seq)

dpsutton17:11:08

the source of which is probably a just a hair snazzier than the solution you are writing or were just about to write

ScArcher17:11:31

got it - (doseq [line (line-seq r)] (println line))

dpsutton17:11:57

right. whatever generates the collection of lines

bringe18:11:43

I have a question that I feel like there is a simple answer to, but it's not clicking. I have a string arg in a function that I want to convert to edn with clojure.edn/read-string. Sometimes the string arg will be a string representation of edn, and sometimes it will just be an actual string, like "hello world". If it's just a normal string, I want to leave it as is, but if it's a string representing edn, I want to call clojure.edn/read-string - because if I call that on a normal string like "hello world" I just get hello returned (a symbol I believe, because it was expecting edn).

dpsutton18:11:47

do you control the source of the string?

bringe18:11:48

So how can I make this work for both strings and edn-as-strings? Do I need to check if the string is edn-as-a-string? How would I do that?

bfabry18:11:02

"hello" is a valid edn string. so it's gunna be a bit hard

dpsutton18:11:12

this mimics when people try to handle a single element and a collection of elements in a single function

bringe18:11:32

No, it's coming from timbre, and I'm adding my own output-fn

dpsutton18:11:49

you could check if its some type of coll

dpsutton18:11:57

if symbol just use the string rep

bringe18:11:45

Oh I think that would work... thanks

bfabry18:11:43

probably not important to you but fwiw that's narrowing your api so that it only accepts a subset of edn. if you have external consumers or whatever you might want to document that

dpsutton18:11:04

although "[DEBUG] no user-id" might give you trouble 🙂

shaun-mahood19:11:54

@brandon.ringe One other thing to watch out for (clojure.edn/read-string "80") will work fine, but (clojure.edn/read-string "08") will throw a NumberFormatException. It caused me all sorts of trouble with one of my early Clojure projects - I was reading in months and it worked totally fine for January - July, but once I hit August the whole thing stopped working properly 🙂

bringe19:11:51

Interesting! Thanks for the heads up.

noisesmith19:11:44

it's the same thing clojure does if you enter 08 at the repl

noisesmith19:11:02

user=> 010
8

bfabry19:11:14

ya, numbers prefixed with 0 are octal and 0x are hex according to java, and so for clojure

noisesmith19:11:46

user=> 36rbatman
683306015

😮 3
noisesmith19:11:01

user=> (Long/toString 683306015 36)
"batman"

Lennart Buit20:11:33

clojure must be the first language that has a literal for base 7

noisesmith20:11:29

010 is base 8 - or do you mean eg. 7r10 ?

Lennart Buit21:11:39

yeah, I find it funny that you can have a strange base like 7 like that

noisesmith19:11:10

or more relevant to the initial point:

user=> 10r08
8

sysarcher21:11:01

Is there a better way to do this?

dpsutton21:11:34

(zipmap (map (comp keyword str) (range)) apps)

dpsutton21:11:49

but keywords on integers aren't great in my opinion

👍 1
sysarcher21:11:19

ooo... I see what you did there... thanks!!

sysarcher21:11:38

you see the type of map I'm looking to generate, right? any suggestions?

dpsutton21:11:11

didn't i make exactly that?

sysarcher21:11:02

no, I was referring to the comment you made about keywords on integers not being great

seancorfield21:11:14

It's poor practice.

seancorfield21:11:11

Even tho' Clojure "allows" stuff like :0 it should be avoided -- either use actual numbers as the keys in the map or use keywords based on symbol names.

seancorfield21:11:31

That has maps with :id and other keys.

sysarcher21:11:49

yeah, I got distracted by this problem... 🙂

seancorfield21:11:28

(map #(hash-map :id %1 :name %2) (range) apps)

👍 1
seancorfield21:11:48

Or mapv if you want a vector of maps rather than a sequence.

sysarcher21:11:48

I found mapv earlier.. 🙂 Thanks for these hints !!

sysarcher21:11:19

ooo... I just got rid of my ugly imperative style for loop I'd written earlier today!! thanks a lot guys

noisesmith23:11:43

also, {0 "yello" 1 "yello2" 2 "yello3"} is allowed

noisesmith23:11:14

you don't get the convenience that the key implicitly calls get, but you can work around that by calling the map, or just using get

👍 1