Fork me on GitHub
#clojure
<
2019-05-15
>
seancorfield00:05:14

You're thinking of peek perhaps?

seancorfield00:05:41

user=> (doc peek)
-------------------------
clojure.core/peek
([coll])
  For a list or queue, same as first, for a vector, same as, but much
  more efficient than, last. If the collection is empty, returns nil.
nil

souenzzo00:05:56

user=> (time (apply min-key val m))
"Elapsed time: 842.254296 msecs"
[0 0]
user=> (time (apply min-key peek m))
"Elapsed time: 838.988506 msecs"
[0 0]
user=> (time (apply min-key last m))
"Elapsed time: 2076.809707 msecs"
[0 0]
user=> (time (apply min-key second m))
"Elapsed time: 1531.45713 msecs"
[0 0]
peek performs like val 🙂 second is slow last is slower

g00:05:24

could someone help me understand what’s happening in the combine step of a fold when it’s called with no arguments?

g00:05:37

having trouble getting my head around it

seancorfield00:05:55

@souenzzo Yup, peek is special-cased for performance and seems to work just fine on MapEntry objects too

user=> (let [x (first {:a 1})] (quick-bench (val x)))
Evaluation count : 91382334 in 6 samples of 15230389 calls.
             Execution time mean : 4.912437 ns
...
user=> (let [x (first {:a 1})] (quick-bench (peek x)))
Evaluation count : 92238174 in 6 samples of 15373029 calls.
             Execution time mean : 4.878394 ns
...

seancorfield00:05:03

user=> (let [x (first {:a 1})] (quick-bench (last x)))
Evaluation count : 4457562 in 6 samples of 742927 calls.
             Execution time mean : 139.544772 ns
...
user=> (let [x (first {:a 1})] (quick-bench (second x)))
Evaluation count : 7887582 in 6 samples of 1314597 calls.
             Execution time mean : 76.811493 ns
...
Big difference in this case.

seancorfield00:05:47

@gtzogana Both the reducef and the combinef can be called with no arguments, and should return the identity element.

seancorfield00:05:30

Since you're looking for a minimum, I would imagine you'd need to return a "maximum" int value so all other values are smaller?

g00:05:27

yeah i think that’s where i got stuck, trying to conceptualize what the identity element of an application of min-keys would be

g00:05:35

but i guess that makes sense

seancorfield00:05:31

It's a little odd in your case @gtzogana because your "identity" is to return an arbitrary MapEntry that contains a maximum value.

seancorfield00:05:43

Your reducef could look like this:

(defn mk ([] (clojure.lang.MapEntry. nil Long/MAX_VALUE)) ([a x] (min-key val a x)))

seancorfield00:05:36

and then you'd do

(r/fold mk (seq your-giant-map))

seancorfield00:05:54

(you need the seq there, otherwise you seem to get a reduce-kv style call of your reducef with the accumulator, the key, and then value)

seancorfield00:05:30

(although I wonder if calling seq here defeats some of what r/fold can do? I haven't experimented much with it...)

g01:05:52

hmm, ok, let me give this a try

g02:05:09

so i’ve got this, which at least returns the correct result

(defn faster-mk [m]
  (fold (fn reducef
          ([] [nil Long/MAX_VALUE])
          ([a b] (min-key second a b))
          ([a b c] (min-key second a [b c])))
        (seq m)))

g02:05:37

but it seems to perform worse than the serial version, even for large sizes of m. tbc

g02:05:52

you may be right about the seq.

g02:05:53

in fact, it’s on the order of 100x worse

g02:05:46

actually, the results are unclear. still testing

seancorfield02:05:49

Use (clojure.lang.MapEntry. nil Long/MAX_VALUE) and (clojure.lang.MapEntry. b c) instead of vectors. Use val instead of second. Don't seq the map.

seancorfield02:05:03

(defn faster-mk [m]
  (fold (fn reducef
          ([] (clojure.lang.MapEntry. nil Long/MAX_VALUE))
          ([a b] (min-key val a b))
          ([a b c] (min-key val a (clojure.lang.MapEntry. b c))))
        m))
that should be faster

seancorfield02:05:43

(although when I tested (apply min-key val large-map), that was pretty fast)

g02:05:22

yeah, the simple version seems better. interesting

g02:05:37

although, that example helps a lot in understanding fold

seancorfield02:05:24

I tried it with a 20,000 element hash map... I think there's just not enough computation in there to benefit from concurrent execution?

g02:05:30

yeah. seems like it.

g02:05:17

for my usage i don’t think i should optimize for maps consistently larger than 50k.

g02:05:27

all the same, thanks a lot for your help

seancorfield02:05:32

It's an interesting little puzzle 🙂 Happy to help!

seancorfield02:05:10

Once you get up to 50k, there's more of a difference

user=> (let [v (into {} (take 50000 (repeatedly #(clojure.lang.MapEntry. (rand-int 10000) (rand-int 10000)))))] (quick-bench (apply min-key val v)))
Evaluation count : 1206 in 6 samples of 201 calls.
             Execution time mean : 522.122368 µs
...
user=> (let [v (into {} (take 50000 (repeatedly #(clojure.lang.MapEntry. (rand-int 10000) (rand-int 10000)))))] (quick-bench (r/fold mk v)))
Evaluation count : 3420 in 6 samples of 570 calls.
             Execution time mean : 172.390103 µs
...

seancorfield02:05:59

(weirdly I'm seeing more of a difference at 20k than I did before -- benchmarking can be weird)

g03:05:48

would it be bad practice to check how big a thing is before deciding how to operate on it?

g03:05:21

(if (> (count thing) 20000) (parallel) (serial)))

seancorfield03:05:00

@gtzogana If you know the performance changes at a particular size, that seems very reasonable.

👍 8
jeroenvandijk08:05:36

I'm observing weird behaviour around watches on a ref. Any idea what's wrong? https://gist.github.com/jeroenvandijk/90650bd5d4b8e3ba346e3483bf70d3ab

jeroenvandijk08:05:28

(let []
  (def ^{:flag 1} v 1))

(def ^{:flag 2} v 2)

(add-watch #'v ::listener (fn [_key _ref old-value new-value]
                            (println "old " old-value ", new" new-value "new-meta " _ref (select-keys (meta _ref) [:flag]))))
 
Wrapping the def in a let block "fixes" the meta assignment

joost-diepenmaat09:05:04

the watch function will be called before the var’s value will be set

jeroenvandijk09:05:59

that doesn't seem to be the case for when it is wrapped in the let block

jeroenvandijk09:05:12

there is an inconsistency if i didn't make a mistake myself

jeroenvandijk09:05:50

In this gist you can see a sequence of evaluations that show this inconsistent behaviour

joost-diepenmaat09:05:12

yeah seems there’s a difference indeed

joost-diepenmaat09:05:26

though this probably falls under “that’s not what you’re supposed to use it ffor”

joost-diepenmaat09:05:29

you’re also not guaranteed to have the state of the ref immediately before or after setting the value

jeroenvandijk09:05:22

yeah i can imagine combining meta, watches and vars is not very common. You might be right 🙂

joost-diepenmaat09:05:24

" Note that an atom’s or ref’s state may have changed again prior to the fn call, so use old/new-state rather than derefing the reference. Note also that watch fns may be called from multiple threads simultaneously”

jaihindhreddy09:05:03

Sorry for interjecting but I believe we are talking about vars here, not refs.

jeroenvandijk09:05:12

But note the value of the var (`_ref`) is given to the watch function so it's not doing a deref

jaihindhreddy09:05:48

No, the _ref argument is the box the holds a value

jaihindhreddy09:05:52

Not the value itself

jaihindhreddy09:05:10

The third and fourth args are old and new values in the box which is the second arg

jeroenvandijk09:05:14

The value of the ref i mean

joost-diepenmaat09:05:14

yes but the meta data on the var is mutable.

joost-diepenmaat09:05:27

and you’re setting the meta of the var, not of the value

jaihindhreddy09:05:50

Exactly. This meta is on the box (var), not the value in the box

jaihindhreddy09:05:10

Still doesn't explain the different behaviour in the let though

jeroenvandijk09:05:27

so (meta some-var) is doing an implicit deref on some-var if that's correct

jaihindhreddy09:05:32

I found the same thing's happening in my REPL:

user=> (declare a)
#'user/a
user=> (add-watch #'a ::listener (fn [_ ref old-val new-val] (pprint {:old-val old-val :new-val new-val :meta (select-keys (meta ref) [:flag])})))
#'user/a
user=> (def ^{:flag 1} a 1)
{:old-val
 #object[clojure.lang.Var$Unbound 0x5e26f1ed "Unbound: #'user/a"],
 :new-val 1,
 :meta {}}
#'user/a
user=> (def ^{:flag 2} a 2)
{:old-val 1, :new-val 2, :meta {:flag 1}}
#'user/a
user=> (def ^{:flag 3} a 3)
{:old-val 2, :new-val 3, :meta {:flag 2}}
#'user/a
user=> (let [] (def ^{:flag 4} a 4))
{:old-val 3, :new-val 4, :meta {:flag 4}}
#'user/a
user=> (def ^{:flag 5} a 5)
{:old-val 4, :new-val 5, :meta {:flag 4}}
#'user/a
user=> 

joost-diepenmaat09:05:56

vars have an identity, and you can change the metadata on the var, so yeah it works like deref, but it’s not a deref since the deref gets the associated value instead of the metadata

jaihindhreddy09:05:56

The symbol some-var gets evaluated to a var(box) which is then deref'ed and the metadata on that value is given

joost-diepenmaat09:05:05

not really in this case

joost-diepenmaat09:05:25

ref here is really #'a

4
jaihindhreddy09:05:09

user=> (def ^{:var-meta 1} a ^{:value-meta 2} {:value-itself 3})
{:old-val 5, :new-val {:value-itself 3}, :meta {:flag 5}}
#'user/a
user=> (meta a)
{:value-meta 2}
user=> (meta #'a)
{:var-meta 1, :line 1, :column 1, :file "NO_SOURCE_PATH", :name a, :ns #object[clojure.lang.Namespace 0x1cf336fd "user"]}
user=> 

jaihindhreddy09:05:20

This might elucidate the difference.

joost-diepenmaat09:05:55

so you’re dealing with the var a and that var has metadata which can be set directly (by def in this case)

joost-diepenmaat09:05:39

you can also set the meta data using alter-meta!

👍 4
jeroenvandijk09:05:36

Thanks guys. I'm trying to make something work for a naive user that set a meta flag on a var so options like alter-meta! are good, but not what i'm after. Maybe I'll have to manage expectations instead!

bronsa09:05:11

redeffing a var with def is not a good idea anyway (other than in dev)

4
jeroenvandijk09:05:39

I don't think i'm doing explicit deref-ing. It's documented that watches support var's here and they get the var https://clojuredocs.org/clojure.core/add-watch . But if (meta some-var) is an implicit deref you might be right

jeroenvandijk09:05:20

or maybe i should not rely on the metadata of a var, since like Joost said, it is a mutable reference

joost-diepenmaat10:05:49

If you can, you might want to switch to using the meta data on the value instead.

joost-diepenmaat10:05:09

Depends on your use case of course

jeroenvandijk10:05:27

I'm doing weird stuff with monitoring vars so I don't have that option for this use case

Ivan Koz10:05:35

any ideas why map-indexed 3 times faster on avg 580 vs 140 ms for 1m entries? (map vector (iterate inc 0) v) (map-indexed #(vector %1 %2) v)

dominicm11:05:03

@nxtk map does not handle chunks when given multiple sequences, that might be why.

funkrider12:05:12

Morning all - I am considering starting a side project to create a clojure to Dart compiler mainly for fun but also with mind to use it to create Flutter apps. What would be the best compiler to work from as a base - Clojure, ClojureScript or CLR? Dart is single threaded so more like JavaScript but the syntax is closer to java/C#. Thoughts?

Jakub Holý (HolyJak)20:05:44

Hi, I am no expert but I would believe that Cljs is best because it was written after Clojure, with lessons taken from it (e.g. relies heavily on protocols rather than Java interfaces). Also. Clj and CLR emit bytecode while you likely want to emit code. Cljs uses Google Closure heavily - not sure whether there is something similar for Dart.

István Karaszi12:05:32

Hi everybody, I got an issue what I don’t really understand. I am trying to import a protocol to my namespace and then use satisfies? to check an instance. In my ns I have the following: (:import taoensso.carmine.connections.IConnectionPool), but when I try to use it like (satisfies? IConnectionPool "") then it throws an NPE

István Karaszi12:05:17

clojure.core/eval          core.clj: 3214
                             ...
      boot.user$eval34439.invoke                  :    1
boot.user$eval34439.invokeStatic                  :    1
         clojure.core/satisfies?  core_deftype.clj:  569
 clojure.core/find-protocol-impl  core_deftype.clj:  536
          clojure.core/instance?          core.clj:  144
java.lang.NullPointerException:

István Karaszi12:05:20

am I doing something wrong?

Alex Miller (Clojure team)12:05:30

For use in this way, you should require, not import

Alex Miller (Clojure team)12:05:45

It’s a little confusing, but IConnectionPool here is both a var (the protocol map), and an interface (generates internally)

Alex Miller (Clojure team)12:05:01

satisfies expects the former

István Karaszi13:05:37

thank you @alexmiller it fixed the problem

István Karaszi13:05:55

thanks @thenonameguy I’ve checked the http://clojuredocs.org first if there is an example like that

István Karaszi13:05:07

but there have been none yet 🙂

kszabo13:05:21

making the world a better place, one clojuredoc at a time 😛

octahedrion13:05:10

what's the latest preferred way to profile Clojure code ? I want something similar to Smalltalk's MessageTally which returns a tree with time spent at each branch (in Smalltalk's case that's messages in Clojure it would be function applications)

urzds17:05:32

Hello! Is this a known issue with Clojure 1.10.0?

user=> (declare foo)
#'user/foo
user=> (def wrap-foo (partial foo "hello"))
#'user/wrap-foo
user=> (defn foo [s] (print (str s " world!")))
#'user/foo
user=> (wrap-foo)
Execution error (IllegalStateException) at user/eval2006 (REPL:1).
Attempting to call unbound fn: #'user/foo

hiredman17:05:45

that isn't a bug

hiredman17:05:20

partial is a function, which means its arguments are evaluated

hiredman17:05:33

foo is a declared var that doesn't have a value

hiredman17:05:07

so when evaluated the "foo" passed to partial, the result is a special object designed to catch these kind of bugs when it is invoked

urzds17:05:39

Why does it not throw right away?

urzds17:05:04

i.e. when defining wrap-foo

hiredman17:05:12

because then it would have to operate in such a way as to make declare useless

Jimmy Miller17:05:37

Clojure 1.10.0
user=> (declare foo)
#'user/foo
user=> (def wrap-foo (partial (var foo) "hello"))
#'user/wrap-foo
user=> (defn foo [s] (print (str s " world!")))
#'user/foo
user=> (wrap-foo)
hello world!nil
Just to show what @hiredman is saying even more clear. If instead of foo you refer to the var, it works how you want it to.

Jimmy Miller17:05:07

The var here defers the look up.

urzds17:05:10

Ah, so when I declare something I have to use var?

hiredman17:05:14

user=> (declare foo)
#'user/foo
user=> (def f #(foo "bar"))
#'user/f
user=> (defn foo [s] (str "foo" s))
#'user/foo
user=> (f)
"foobar"
user=>

hiredman17:05:02

using partial at the top level like without understanding that partial is just a function, so it's arguments are evaluated like any other function at call time is a common source of bugs

Jimmy Miller17:05:04

@urzds Not sure why you are writing code like this. If you are just playing around with things, sure. But I wouldn't expect to see declare in actual code.

hiredman17:05:09

user=> (def x 1)
#'user/x
user=> (def f (partial + x))
#'user/f
user=> (def x 3)
#'user/x
user=> (f 0)
1
user=>

urzds17:05:24

Thanks. So I generally should use var in this case? Or is there a better / more recommended way?

noisesmith17:05:58

@urzds what are you trying to do? just define things out of order (that's the usual usage of declare)

noisesmith17:05:18

usually instead of using (var x) explicitly we use the reader macro #'x which expands to the same thing

hiredman17:05:37

the best advice is don't use partial for top level functions like that

urzds17:05:37

@jimmy I have a map that contains coercion functions as values (and the keys they shall be applied to as keys). And one of these coercion functions calls other functions that will eventually make use the the coercion map again. So in theory this setup is cyclic, but in practice it is not, because the data structures it operates on are not cyclic.

noisesmith17:05:25

@urzds if the functions can all be defined in one form, there's letfn that allows cyclical references between the functions it creates in local scope

noisesmith18:05:14

another variant is to have every fn take an argument containing a hash-map that includes all the other fns - that way, you can even add more functions at runtime or from another namespace

urzds18:05:39

So the data structure I operate on looks like this:

[{:foo 1
  :bar {:foo 2 ...}}
 {:foo 3 ...}]
And in these few instances where the object contains a "sub object" in the :bar key, I want to apply the same coercions to the sub object, which is why I need access to the coercion map.

noisesmith18:05:14

sounds like it could be a job for clojure.walk/prewalk with a function testing for map entries and calling a multimethod on key

urzds18:05:57

Hm, thanks. I never considered this to be a full walk, because :foo 2, the object under :bar, is of a different type that never contains other sub objects.

noisesmith18:05:25

prewalk is a blunt tool, agreed, but it might lead to simpler code

urzds18:05:50

I will try that. 🙂

noisesmith18:05:48

one more note: I specify prewalk rather than postwalk specifically because you can't test for map-entries in postwalk (the code turns them into two element vectors, and that works fine for hash-map, but it removes a distinction you likely need to check for, prewalk avoids that issue)

urzds18:05:02

Might reduce the amount of code.

peterdee18:05:32

I am trying to create a vector-based zipper where the children of a node are (next node). For navigating around, this works fine:

(defn vec2zip
    "Return a zipper where the children are (next node)."
      [root]
     (z/zipper vector? next (fn [n c] (into n c)) root))
But editing doesn't work as I'd expect:
(-> (vec2zip [:X [:A] [:B] [:C]]) z/down (z/insert-child [:a]) z/root)
[:X [:A] [:B] [:C] [:A [:a]] [:B] [:C]]
Am I doing something stupid? I tried to attach metadata in the children and make-node functions, but it doesn't seem to stick. Might that be the problem?

noisesmith18:05:20

@peterd have you looked at z/vector-zip? - or is the point to implement this from scratch as an exercise?

hiredman18:05:49

because n is the existing vector

peterdee18:05:00

I looked at z/vector-zip but it really doesn't seem to have the semantics I want.

hiredman18:05:13

you need something like (into [(nth n 0)] c)

4
peterdee18:05:32

@noisesmith the children don't 'have names', if that makes sense.

peterdee18:05:44

@hiredman let me try this.

hiredman18:05:34

I would strongly recommend using maps instead of vectors for tagged data like this

hiredman18:05:25

the built in xml-zip basically has the semantics you want

hiredman18:05:41

(using maps with {:tag tag :children children})

peterdee18:05:20

@hiredman Yeah, I have that working just fine with {:n :X :c [{:n :A} {:n :B} {:n :C}]} for example. Just gets a bit busy.

peterdee18:05:46

(defn map2zip
  "Return a zipper for a map representing a plan."
  [root]
  (z/zipper map? :c (fn [n c] (assoc n :c (vec c))) root))

peterdee19:05:21

Thanks @noisesmith and @hiredman. I've tried everything I can think of. Given what I've seen, I agree that maps are the best approach.

lispyclouds19:05:53

Hello, what is the equivalent of :java-source-paths of leiningen in tools.deps? Some of my code is in Java and I really wanna use it as it is.

Alex Miller (Clojure team)19:05:49

tools.deps does not support java compilation

Alex Miller (Clojure team)19:05:09

you can use it in combination with javac etc if you want that to work

Alex Miller (Clojure team)19:05:34

or you can split your project into java-only and clojure-only parts and have the latter depend on the former

lispyclouds19:05:02

ah okay. Thanks for the info @alexmiller Is java compilation planned soon? Really love using tools.deps!

Alex Miller (Clojure team)19:05:54

not planned - deps does a) dependency resolution, b) classpath building, c) program launching

lispyclouds19:05:54

got it. thanks again 😄

pshk4r19:05:18

Are there any books on Clojure that explain it's philosophy as opposed to just teaching syntax? I am interested in learning about choices Rich made and trade-offs resulting from those choices. I know he talks about it in his talks but I would like (if it exists) to have a single place with all this information, so to say. To understand Clojure, not only to learn it

Alex Miller (Clojure team)19:05:16

probably the best you'll get for that are either talks by Rich, or the http://clojure.org reference pages, or in book form - Programming Clojure (written by Stu Halloway and latest revised by me, both of us working with Rich)

pshk4r19:05:18

It's amazing to get an answer in less than a minute, and especially from you Alex! 🙂 Thank you! I love this community ❤️

Alex Miller (Clojure team)20:05:56

sibling dirs contain other relevant stuff probably

pshk4r20:05:56

thank you!

Alex Miller (Clojure team)20:05:00

that was missing some videos so I added them and reverse date sorted

Alex Miller (Clojure team)20:05:13

the talk Rich did at JavaOne years ago is actually a pretty great intro https://www.youtube.com/watch?v=VSdnJDO-xdg

Alex Miller (Clojure team)20:05:36

not in learning the language, but why the language exists

pshk4r20:05:17

Thank you, again! Meanwhile, I've ordered your book 😊

Alex Miller (Clojure team)20:05:39

they make great gifts - buy 10!

😃 4
seancorfield20:05:31

Also @U0DNK8PV0 you can get quite a good sense of philosophy and the "Why?" of Clojure from The Joy Of Clojure book, if you haven't already read that.

👍 8
😹 4
razum2um20:05:41

Asking for the most ideomatic approach: suppose we have a mutual recursion

(defn a [x] (if .. (b x))
(defn b [x] (if ...(a x))
Now I’d like to inject another arg y into a but in order to do so I also have to: 1) change b arity (in case b call a not directly but there’s a longer call chain it gets tedious). 2) Another variant is introducing a dynamic var and do binding across everything (but it becomes opaque and var always means toplevel) 3) in case x is even not {} I could handle it with metadata containing y (looks like a hack) What would you prefer? (please vote) Am I missing yet another approach? In short: 1=explicit pass args | 2=implicit | 3=hack

1️⃣ 36
2️⃣ 4
3️⃣ 8
petterik20:05:13

3) Use (declare b) before defining a?

razum2um20:05:13

Sorry, maybe I expressed myself not clearly, I know how to define/declare, the question is actually -> pass something explicit or implicit

art10:05:08

Well regardless of the problem context, if you want functions calling each other on the same level, usually something is wrong. It’s always good to call functions from top to bottom.

/ a
foo
    \ b
My bet is you just need a proper data/context shape + (recur) like
(defn foo
  [ctx]
  (recur
    (cond (condition)
      :a (a ctx)
      :b (b ctx))))

(defn a
  [ctx]
  (assoc ctx ...))

(defn b
  [ctx]
  (assoc ctx ...))

👍 4
art10:05:45

Again, it’s hard to guess but the rule is simple and works all the time just great.

Noah Bogart20:05:29

can you write out the two implementations?

razum2um20:05:00

maybe ideal situation is when x is a {} and passing around (assoc x) is very cool, but in this case it’s not 😕

benoit21:05:28

For this specific example you linked to I would get rid of those auxiliary functions. The 4 arguments passed around add a lot of cognitive overhead and the body of these functions don't do much.

razum2um21:05:43

yep, a valid point 👍 but still, it’s not always an option to incorporate everything inside a single scope

razum2um21:05:12

but I also aware that recur is more optimal than trampolines (which are even omitted in this case -> stackoverflow possible)

razum2um21:05:53

still, people often argue about scala implicits but they’re really handy instead of passing smth around

benoit21:05:34

I agree, I use dynamic vars when I have a context to pass everywhere or to pass through a long list of functions that do nothing with it other than forwarding it.

jaihindhreddy07:05:10

Even when there is non-trivial state that may be changed by users, passing explicit args is better IMHO. We can get back the convenience of not having to pass args each time by implementing Component and/or Integrant's abstractions.

razum2um20:05:00

maybe ideal situation is when x is a {} and passing around (assoc x) is very cool, but in this case it’s not 😕

Alex20:05:49

Given it's mutual recursion with two fns colocated next to each other in the same file and calling each other directly, I'd add an explicit argument (and maybe another arity so clients can maintain their present interface)

razum2um21:05:24

yeah, preserving interface makes 1. even more verbose 😞 this situation makes me think one more time that only passing hashes around rocks

bhurlow20:05:58

hey ya'll, what are people using for repl autocomplete etc when using just clj instead of leiningen? Is reply still common?

razum2um21:05:24

use another main 🙂 https://github.com/bhauman/rebel-readline (uses compliment library for autocomplete, same as reply afair)

razum2um21:05:53

still, people often argue about scala implicits but they’re really handy instead of passing smth around

emccue21:05:25

@razum2um I'm still not super clear about the context here

emccue21:05:40

does your "y" change over the course of the mutual recursion?

razum2um21:05:49

yes it does, but only in a; b could only pass it around