Fork me on GitHub
#beginners
<
2019-05-03
>
michaeljameserwin15:05:34

What is this form of function body called:

(defn my-fn [args]
  {value (<body>)
   other-value (<body>)})
googling has not helped me find this šŸ˜‰

bronsa15:05:22

which part do you think has a canonical name?

bronsa15:05:33

I'm struggling to understand what part you want to find the name of

manutter5115:05:49

That's just a function that returns a map, there's not really a special name for it.

michaeljameserwin15:05:14

so it is just caught me off guard by the way it was written

bronsa15:05:32

it's perfectly idiomatic FWIW

bronsa15:05:43

I don't see anything weird or unexpected with it

manutter5115:05:58

Yes, if you're used to languages that use an explicit return statement to return a value, it can be surprising.

bronsa15:05:16

would rather have seen e.g. (let [x .. y ..] {k1 x k2 y}) instead?

michaeljameserwin15:05:23

ya definitely, just threw me off, just maybe not enough coffee

manutter5115:05:29

Here's a function that returns the number 42:

(defn answer-to-life []
  42)

manutter5115:05:20

You have to have something after the square brackets, even if it's only nil, but other than that you can put any valid Clojure expression.

bronsa15:05:32

nah, nil is implicit :)

bronsa15:05:37

(defn foo []) is valid

manutter5115:05:07

Whoa, son of a gun, it is!

manutter5115:05:42

Ah well, I've only been using Clojure since version 1.2, can't expect me to know everything yet šŸ˜‚

michaeljameserwin15:05:10

tbh I was debugging someone else's code (2nd month of a FT clojure job as a somewhat clojure newbie) and there's actually a bug in how this fn is called so I was like "is there some special form here I'm missing?" --- haha... no, it's just a map...

bronsa16:05:53

we've all been there :)

mkeller0216:05:51

Question: If I did count on a large, lazy sequence, it will release it's references to the elements as it goes along (guess I'm assuming that there are lazy sequences such that an O(1) shortcut isn't possible). That is...it's not going to try to keep a huge sequence in memory? I guess I can try to crash and find out...but maybe somebody has something relevant to add

christian.gonzalez16:05:46

You can try (count (range)) and the operation will timeout. (range) returns an infinite sequence.

mkeller0216:05:55

Got it, so the answer is "yes, it will hold the whole or much in memory" ? Otherwise, it seems like it would just hang on the above as the garbage collector furiously cleans up

mkeller0216:05:53

Oh, wait, but you said time out...it will time out then, not OOM error

christian.gonzalez16:05:59

I believe it would hold the entire thing in memory.

christian.gonzalez16:05:08

I'm not sure I ctrl+c'd after a minute

christian.gonzalez16:05:11

I would see if it goes OOM

christian.gonzalez16:05:49

I think once you realize the entirety of a lazy-sequence then it becomes non-lazy. Otherwise it will cache the realized results, and hold off on the rest. So when you try to realize the entirety of range it will try to hold it all in memory. (Someone correct me if I'm wrong)

noisesmith16:05:15

@christian.gonzalez @mkeller02 it will eagerly realize all the elements, but will not hold them in memory

noisesmith16:05:29

they will be held in memory if some other binding holds onto the head though

noisesmith16:05:54

(count (range)) doesn't OOM, eventually it gets a numeric exception iirc

noisesmith16:05:39

(though if it kept going with bignums instead of overflowing the numeric type, eventually you'd produce a number too big to fit in memory...)

mkeller0216:05:16

Excellent, that's good info (and makes sense)

noisesmith16:05:21

@christian.gonzalez realizing a lazy data type doesn't make it non-lazy, it just makes it fully realized

noisesmith16:05:02

it always caches elements that are realized, but the gc can drop them anyway if there is no path to them via live objects

christian.gonzalez16:05:23

though if you retain the head, the path is still there right

k.i.o16:05:55

storing seq in Var counts as retaining head?

christian.gonzalez16:05:02

I think something like doall will walk the lazy sequence, realize it, retain the head, and make it non-lazy

noisesmith16:05:55

doall does not retain the head or make it non-lazy

christian.gonzalez16:05:49

doall is all you need. Just because the seq has type LazySeq doesn't mean it has pending evaluation. Lazy seqs cache their results, so all you need to do is walk the lazy seq once (as doall does) in order to force it all, and thus render it non-lazy. seq does not force the entire collection to be evaluated.
https://stackoverflow.com/questions/1641626/how-to-convert-lazy-sequence-to-non-lazy-in-clojure Per the docstrings it mentions that doall retains the head and returns it.

christian.gonzalez16:05:21

I don't work too often with lazy-seqs so I'm just trying to wrap my head around it

noisesmith16:05:55

a forced lazy-seq is still a lazy object - it's just one that has been realized

noisesmith16:05:28

this is more than just a pedantic point because eg. the toString of things that are lazy is annoying and unuseful, and doall doesn't help with that

noisesmith16:05:56

(ins)user=> (str (map inc [1]))
"[email protected]"
(ins)user=> (str (doall (map inc [1])))
"[email protected]"

noisesmith16:05:31

actually making it non-lazy (a different type)

(ins)user=> (str (vec (map inc [1])))
"[2]"

christian.gonzalez16:05:32

ah that makes sense, still a lazy sequence in the end

noisesmith16:05:00

right, LazySeq is a specific class in the vm, and the return value of doall is still an instance of that class

k.i.o16:05:31

well doall just returns the head isnt it?

dougkrieger16:05:49

idk if this is a high quality post, but it seems to touch on relevant questions http://programming-puzzler.blogspot.com/2009/01/laziness-in-clojure-traps-workarounds.html

k.i.o16:05:05

(doall [coll]
   (dorun coll)
   coll)

dpsutton16:05:07

(count (range)) is taking a long time to throw an exception for me

k.i.o16:05:55

because old values being gc'd probably

dougkrieger16:05:58

the tldr seems to be if you have a named reference to your lazy sequence, clojure caches it as it goes. but if it's anonymous, the GC cleans up as it goes

dougkrieger16:05:09

(according to the link I shared above)

dougkrieger16:05:07

(also, disclaimer, it's a 10 year old blog post, so maybe it's improved by now)

noisesmith16:05:48

@dougkrieger that's almost it, but clojure is smart about closed-over things that can be cleaned up as well

noisesmith16:05:13

(let [r (range)] (count r)) - clojure knows that nobody can access r after count starts, and allows its items to be gc'd

noisesmith16:05:41

this is part of why normal debugging of clojure is hard - you actually have to turn the behavior off to make a normal debugger work

noisesmith16:05:49

"locals clearing"

dpsutton16:05:53

i don't think (count (range)) will throw an arithmetic exception.

dpsutton16:05:23

there's a LongRange that will though

noisesmith16:05:28

I wasn't sure if it auto-promoted

noisesmith16:05:51

@dpsutton you won't get longrange unless you specify an "end" arg that is a long, and... that precludes that kind of error I think

dpsutton16:05:58

yeah that sounds right

dpsutton16:05:06

i'm gonna turn my fans off now šŸ™‚

noisesmith16:05:58

aha, the case where you provide no args just uses inc' - so you'd eventually run out of room for the number, yeah

k.i.o16:05:01

yeah 0 - arity range uses arbitrary precision inc'

hiredman18:05:51

count returns an int, so a long range with a size larger then can fit in an int will have some kind of error too

dougkrieger19:05:24

is there a hof in core like "once" that takes a function and will only run it once no matter how many times it's called?

noisesmith19:05:31

there's memoize, which calls your function once per unique arg-list

dougkrieger19:05:34

I suppose memoize would work

dougkrieger19:05:43

ha ok cool, thanks

noisesmith19:05:06

there's also delay, which is defined in terms of a single block of code that is only evaluated once, but not until it's asked for

dougkrieger19:05:49

I have an atom that I want to occasionally redefine initial state, and constantly changing between "def" and "defonce" is annoying. I'm not sure wrapping/unwrapping in memoize would be better

noisesmith19:05:59

if that's the usage, why not a defonce plus a function that assigns its initial state via reset!

dougkrieger19:05:42

that's actually what I'm doing right now, so what I'm really considering is wrapping reset! in memoize and removing memoize when I want to change state

dougkrieger19:05:41

I could just comment out the reset call at the risk of forgetting what I did and messing up production bundles

noisesmith19:05:32

why not put the reset! in a function that isn't called at the top level?

noisesmith19:05:41

(except maybe inside the defonce...)

noisesmith19:05:35

(defn reset-foo [a] ...)
(defonce state (doto (atom {}) reset-foo))

noisesmith19:05:22

then you can easily use it later manually, but it only runs implicitly once

dougkrieger19:05:23

hmm, maybe slurp/vomit is the editor mechanic I need to make this smoother. I could do as you just defined, then vomit the reset-foo call out when I want to revert/change initial state

noisesmith19:05:11

if it's done like above, you don't need to edit the code (beyond changing how reset works of course)- just call reset-foo by hand (redefining as needed) from the repl

dougkrieger19:05:29

ah, true! good idea

noisesmith19:05:14

the general pattern is to use functions to abstract unconditional side effects into functions so a repl user can decide when they run

noisesmith19:05:45

(you can do similar with -main to avoid running vm shutdown stuff in the repl etc.)

noisesmith19:05:16

in fact, I'd usually prefer to do the state-init inside -main rather than at the top level

dougkrieger19:05:31

I wire up state before main. I suppose I could wrap all of that logic inside a function definition though and call that within main

dougkrieger19:05:00

It's helped my repl flow though to have more top-level functions

dougkrieger19:05:20

using defun instead of last-sexp variants of cider commands

noisesmith19:05:24

it's easier to use the repl if you don't complect namespace loading with state initialization (to use one of RH's favorite words)

dougkrieger19:05:24

i.e. have the top level only execute namespace loading, pure function definitions, and call to main?

noisesmith19:05:38

well, the top level shouldn't even call main, but yeah

dougkrieger19:05:02

because jvm implicitly calls main

noisesmith19:05:09

and then anything interesting that main does should be in functions you can call selectively

dougkrieger19:05:47

is the benefit there to enforce this sort of contract that any top-level form (aside from main) can be run in the repl with no side effects?

noisesmith19:05:46

yeah, that's pretty much the idea, there's libraries designed to make this easier to implement (stuartsierra/component, integrant, mount, etc.)

noisesmith19:05:29

you end up with side effects all being defined inside the state object, so if you want to run without one of those side effects all you need is to replace that part of the state object

noisesmith19:05:50

then the state object passes through (or needs to be manually passed to) any code that needs to invoke side effects

dougkrieger19:05:21

(side note: annoying that spacemacs doesn't have nice evil slurp bindings ootb *edit* jk it does)

mario.cordova.86221:05:26

When I run lein with-profile clj,dev test com.foo.bar-bar.automated-tests I get Could not locate etaoin__init.class... on classpath. but I've added [etaoin "0.3.3"] to the dependencies... What else might be the issue?

mario.cordova.86222:05:16

I have the correct directory under :test-paths as well

noisesmith22:05:58

usually using with-profile I want +foo,+bar

noisesmith22:05:21

otherwise you need to explicitly provide all the profiles, even the ones that should be implicit for the task (eg test here)

noisesmith22:05:53

I don't think it will look at test-paths if you don't provide a profile that wants it

duncanmak23:05:57

Hello, Iā€™m used to the cond from Scheme, does the cond in Clojure support the => syntax?

duncanmak23:05:11

like, in Scheme

duncanmak23:05:42

(cond ((re-match ...) => (lambda (match) ...)))

duncanmak23:05:09

if-let feels a bit like it, but it only allow one test

seancorfield23:05:01

(answered in #clojure )