Fork me on GitHub
#beginners
<
2018-06-03
>
bertofer08:06:30

Hi, I am dealing with some state in my library where I find myself doing (swap!) on two or more atoms sequentially, as they represent a different kind of state, but sometimes I have operations that change more than one at same time. Don’t need multi-threading, just 1 thread can handle them. So I think, maybe all of this should be just 1 atom with nested maps. Is it fine / recommended to have atoms with big nested maps, over having smaller maps distributed through different atoms? Is there any “general” rule when facing this kind of situations? Thanks!

val_waeselynck09:06:41

If you need consistency, prefer one composite atom.

val_waeselynck09:06:45

(Or refs for performance critical scenarios, which are fairly rare)

eggsyntax12:06:53

☝️ agreed. On the web end of things especially, there’s been an increasing trend toward holding all state in a single,“database-like” complex nested atom. My team & I have done that on a number of projects, including one that intrinsically has to represent a great deal of state, and have been happy about that approach.

val_waeselynck19:06:09

On the server side, there's rarely any use case for atoms for web stuff. If you're using atoms for mocking database components in memory, prefer several atoms so that you don't rely on more consistency that you will get in production. If you need to make a mutable, non concurrent algorithm, use a volatile instead, which will be faster and make the "this thing is not shared" intent more obvious.

bertofer20:06:22

Hi, my use case is actually keeping the state of subscriptions of a websocket connection. I don’t manage the websocket server, though, only a client. The server offers it’s own semantics for subsribe to channels, and want to keep the subscriptions the user has, it’s state, and sometimes the data coming from the websocket changes certain state

bertofer20:06:10

Thing is, websocket can close, so subscriptions on the “websocket” are lost, but I want to offer reconnection / resubscribing of the channels the library user already subscribed. So the lib user does not have to handle reconnectios / etc. I also need to keep in state the current websocket connection used, ’cause when it closes, a new one has to be created and put into the state to send messages to that one

bertofer20:06:18

So I have 2 “sources” of events (core.async ch), the user and what comes from the ws, and I need to keep the state of the connection, user interactions, websocket subscriptions, …all in a consistent way. I am thinking that the composite atom solution seems the best / simplest

val_waeselynck06:06:44

Note that of you are using core.async, state can also be kept in locals in a go loop, thus avoiding mutations. It depends on how you want to read

bertofer08:06:06

You’re right, it could be an option. I am still figuring out if I need to read from outside or not. Is it easy to test the state if it’s local to the go-loop? Or I wouldn’t test the go-loop itself, but all the changes in a (fn old-state) -> new-state manner independently? I also need to perform some actions depending on the new state when consuming a new message, but that can be solved either having “pending actions” in the state itself, or having 2 local variables

tkjone13:06:19

Hey all, I just started reading about Data Oriented Design. Would people say that this aligns to the goals, or thought processes, of Clojure? I have seen that people are encouraged to think of data while working with Clojure, but, while reading through Data Oriented Design articles, I can't immediately tell if these are the same things. Example talk https://www.youtube.com/watch?v=rX0ItVEVjHc

hello25413:06:36

I’ve watched this video. Mike seems to be talking about the benefits of data-oriented design from a performance standpoint

hello25413:06:07

i.e. reasoning about data first (instead of objects first) makes it easier to think about how to avoid cache misses and so on

tkjone13:06:40

Yes, it appears to usually be referenced when considering high performance game development. Does this thought process overlap with functional programming in any way?

hello25413:06:04

I generally don’t think functional programming (or object-oriented programming for that matter) is a great option if you really need all the performance you can get

hello25413:06:24

They tend to be too far from the metal

tkjone13:06:26

hmmm perhaps not all concepts, but, some can be borrowed. For example, pure functions. Not that pure functions are the only defining characteristic of a functional language, but I imagine there are high performance systems that benefits from lessons of functional programming. My goal, in the end, is to better understand the lessons of these different systems so they can be blended together in a sane way.

hello25413:06:27

That said, I do like to program in a data-oriented approach. I find writing programs as data + data transformations jives really well with how I do things

hello25413:06:17

> I imagine there are high performance systems that benefits from lessons of functional programming. That’s certainly true. Rust is a good modern example of that I think.

smnplk03:06:52

@ Data oriented design is a performance optimisation and should not be confused with data driven design

ryan.russell01115:06:48

so maybe kind of a silly question, but I'm curious... what is the actual difference between using .toLowerCase vs using clojure.string/lower-case?

lockdown-15:06:11

the latter is a wrapper for the former

ryan.russell01115:06:55

I figured that ... so in that case.. why use the latter at all rather than just use the Java class directly?

lockdown-15:06:44

the latter is more general and clojury, it calls toString on its argument first

ryan.russell01115:06:15

so would you say that in a Clojure environment... using the latter would be safer?

lockdown-15:06:52

I would say its more idiomatic

lockdown-15:06:26

the clojure one will work with anything that has a toString method

ryan.russell01115:06:15

ah gotcha.. thanks! As minor as a thing like that seems to be, I'm just trying to figure out the reasoning behind decisions to use what and when.

lockdown-15:06:02

use the clojure one since its already type hinted

lockdown-15:06:17

normally, if the clojure std lib has a function for a java method use the clojure one

ryan.russell01115:06:48

that's fair. Thank you!

alexmiller16:06:50

Also, the Clojure one is portable to Clojurescript

jumar16:06:48

@ryan.russell011 also you can treat lower-case as a first class function, which is not possible with .toLowerCase:

(map string/lower-case ["Alice" "Bob"])
.toLowerCase requires a wrapper
(map #(.toLowerCase %) ["Alice" "Bob"])

ryan.russell01116:06:24

ah.. also a good point.. wasn't thinking about that aspect

sova19:06:29

Hi I'd like an under the radar clojure function that is very useful to you

noisesmith20:06:44

juxt is underrated

sova20:06:46

oo could you tell me about juxt?

sova20:06:17

fnil i have found very useful for the case where i'm conj'ing into a #{set}

noisesmith20:06:28

juxt takes N functions and returns a function calls each function on its args and puts them in a vector

noisesmith20:06:20

user=> ((juxt - * / +) 42 3)
[39 126 14 45]

noisesmith20:06:01

user=> ((juxt - * / + > <) 42 3 1)
[38 126 14 46 true false]

sova20:06:27

easier to reason about when i think of it as applying the function to the params in columns

noisesmith20:06:11

right, each function you give it generates a column, calling the resulting function gives you a row

sova20:06:32

that's very interesting. one could store a lot of information in juxt result vectors ... with meaningful juxt fns

sova20:06:59

I am confused by

noisesmith20:06:41

yup that's a classic use for juxt

sova20:06:14

the first col is the result of take 3

sova20:06:19

and the second the result of drop 3

sova20:06:23

i would like to make a reference one day of clojure phrase synonyms

sova20:06:48

sometimes there are many ways to achieve the same effect

sova20:06:14

so juxt can be a million fns

sova20:06:38

i'm trying to stretch my imagination since sometimes i assume limits where there are none! xD

noisesmith20:06:44

@sova here's a macro that's very useful when debugging

(defmacro locals
  []
  (into {}
        (map (juxt (comp keyword name)
                   identity))
        (keys &env)))

noisesmith20:06:53

(speaking of juxt)

sova20:06:25

okay, looks cool. spits out a list of local vars?

noisesmith20:06:00

ledger.balance-loader.unit-tests=> (defn foo [x] (let [y (+ x x)] (println (locals)) y))
#'ledger.balance-loader.unit-tests/foo
ledger.balance-loader.unit-tests=> (foo 2)
{:x 2, :y 4}
4

jumar07:06:00

Really cool - thank you very much for this! Here's also an interesting implementation of a similar function: https://www.safaribooksonline.com/library/view/clojure-programming/9781449310387/ch05s08.html

sova20:06:57

wow cool

noisesmith20:06:40

very useful for debugging for obvious reasons

noisesmith20:06:32

one of the rare macros that has no use or need for quoting as well

sova20:06:39

noisesmith, you just made my day

sova20:06:51

thank you again kind fellow, that is so sweet

sova20:06:14

can you explain to me why i only need to call (locals) within a fn and not pass it anything? like... i get that they share scope but i don't understand how exactly

noisesmith20:06:54

scope is nested - you can see surrounding context from a macro, through the implicit &env binding

noisesmith20:06:08

(it's actually an invisible arg passed into the macro by the compiler)

noisesmith20:06:42

there's also a &form binding which gives you the raw form which the macro saw before running

sova20:06:02

wow, cool! &env and &form

sova20:06:14

i feel like i'm looking inside clojure's brain

sova20:06:00

are there any others than &env and &form?

sova20:06:24

thank you @victor.cleja

victor.cleja20:06:05

And, yes, juxt is pretty cool

noisesmith20:06:14

ledger.balance-loader.unit-tests=> (defmacro wizardry! [& whatever] (prn ((juxt type (partial map type) identity) &form)))
#'ledger.balance-loader.unit-tests/wizardry!
ledger.balance-loader.unit-tests=> (wizardry! 1 2 3)
[clojure.lang.PersistentList (clojure.lang.Symbol java.lang.Long java.lang.Long java.lang.Long) (wizardry! 1 2 3)]
nil

noisesmith20:06:07

it's fun trivia, maybe not apropriate #beginners material

sova20:06:22

damn so cool. the symbols

sova20:06:34

no it's perfect, i'm trying to expand my knowledge on the whole #!

sova20:06:06

so clojure.lang.Symbol and stuff, the compiler uses these to arrange everything properly and through some magic makes computations happen?

noisesmith20:06:24

notice I used juxt again there

noisesmith20:06:33

I asked it for the type of &form - list

noisesmith20:06:47

then (partial map type) - you know what that would return, right?

noisesmith21:06:55

so what that's showing is that &form contains a list of the values in the macro call before anything is done to them, including the macro symbol itself

sova21:06:50

quick question, is it correct to say (partial map form) is a transducer?

sova21:06:28

oh i see. juxt and partial play together nicely

sova21:06:49

super sweet!

noisesmith21:06:58

no, (partial map f) is a function, that takes 1 or more collections and maps f over them

noisesmith21:06:11

if you called it with no args, it would return a transducer

noisesmith21:06:37

and the transducer would map f over its input (of whatever sort that might be)

tkjone21:06:14

I have a vector like this: [1 2 3 4 5 6] and I want to turn the first two indices into a nested vector [[ 1 2] 3 4 5 6] -> my solutions have all a little too clunky. For example:

(into (vector ((juxt first second) [1 2 3 4 5 6])) (drop 2 [1 2 3 4 5 6]))

noisesmith21:06:02

=> (apply into ((juxt (comp vector (partial take 2)) (partial drop 2)) [1 2 3 4 5 6]))
[(1 2) 3 4 5 6]

noisesmith21:06:55

this is a case where likely the right solution is to use a simple let block, instead of funky higher order fixed point functions

tkjone21:06:15

That's good to know, I figured that because I want to consistently do this to this particular piece of data that it might be best to figure out something "functional". Good to know

noisesmith21:06:04

a let block is still functional, it just trades the elegance of anonymous application for the clarity of separating steps explicitly

sundarj21:06:22

how about this? @tkjone

=> (apply vector (vec (take 2 v)) (drop 2 v))
[[1 2] 3 4 5 6]

tkjone21:06:55

Yep! Thats certainly much nicer than mine

sundarj21:06:29

(vec (take 2 v)) can be replaced with (subvec v 0 2)

noisesmith21:06:54

if you know v is guaranteed to be a vector, yes

sundarj21:06:45

there might be some trickery you can do with (split-at 2 v)

sundarj21:06:31

=> (reduce conj (split-at 2 v))
((3 4 5 6) 1 2)
hah

sundarj22:06:12

=> (reduce conj (rseq (split-at 2 v)))
((1 2) 3 4 5 6)
close enough

sundarj22:06:54

first time the 2-arity version of reduce has come in useful 😁