Fork me on GitHub
#beginners
<
2024-01-05
>
Jim Newton12:01:47

I always mix up the default clause syntax of case vs cond. with cond the default case is indicated by :else whereas with case the default case is indicated by a missing object-to-match

dpsutton12:01:48

cond always takes a predicate. people use :else because it is truthy. But it’s always even pairs with predicate clause

1
dpsutton12:01:13

:else is not special . it is not syntax

1
dpsutton12:01:25

> Takes a set of test/expr pairs. It evaluates each test one at a > time. If a test returns logical true, cond evaluates and returns > the value of the corresponding expr and doesn’t evaluate any of the > other tests or exprs. (cond) returns nil.

dpsutton12:01:47

cond couldn’t be simpler: takes pairs, goes through them evaluating test for the first one that returns truthy

Jim Newton13:01:01

I suppose cond could be a tiny bit simpler. If you omit the :else place holder, cond could still try to evaluate the test clause, and simply return its value if there's no consequence clause.

Jim Newton13:01:13

Then case and cond would be consistent

Jim Newton13:01:19

in Common Lisp the consequence clause is optional. if it is missing the value of the test expression is returned. emacs lisp cond works the same way as Common Lisp cond

Jim Newton13:01:17

I once implemented cl-cond in clojure. of course I don't use it because it would not be idiomatic

dpsutton13:01:48

common lisp uses grouping parens that Clojure does not.

dpsutton13:01:32

(cond
  (even? x) :even
  :else     :odd)
is clojure vs something like
(cond
  ((even? x) :even)
  (:odd))

Jim Newton13:01:46

yes, so does elisp. however, clojure's cond could have been implemented so that the final consequent were optional.

Jim Newton13:01:57

similar to case

Jim Newton13:01:07

oh well, that's water under the bridge

Jim Newton13:01:25

(cond
  (test-1) (consequent-1)

  (test-2) (consequent-2)

  (consequent-3))

Jim Newton13:01:32

anyway every time I come back to clojure after being away for several months, I have to re-look-up the syntax for case and cond

delaguardo13:01:02

you can always add your own syntax:

(defmacro cl-cond [& body]
  (let [body' (partition-all 2 body)]
    (if (= 1 (count (last body')))
      `(or
        (cond ~@(butlast body))
        ~(last body))
      `(cond ~@body))))

(cl-cond
  (= 1 2) 42
  (= 1 1) 43
  44)
;; => 43

(cl-cond
  (= 1 2) 42
  (= 1 3) 43
  44)
;; => 44

(cl-cond
  (= 1 2) 42
  (= 1 3) 43)
;; => nil

(cl-cond
  (= 1 2) 42
  (= 1 1) 43)
;; => 43

Ingy döt Net19:01:57

It seems to me that clojure.core/cond behavior still could be changed to accept an odd number of arguments in a future version.

Noah Bogart15:01:46

i think it's a rite of passage to write your own version of cond lol, cuz i also did that

Noah Bogart19:01:35

I should note that stylistically, I always add an ; else comment before the final branch of my case and condp forms. I like to have the visual consistency

Ingy döt Net13:01:43

you could also do this:

(case kw
  :a (foo)
  :b (bar)
  #_else (throw ...))
if it better matches your style.

Jim Newton13:01:38

comment: clojure future is remarkably easy to use compared to what hoops I have to jump through in Python.

delaguardo13:01:35

even if using threading module? I didn't touch python for a while but I recall something similar as future could be done like that:

>>> import time
>>> from threading import Thread
>>> def foo():
...     time.sleep(2)
...     print("DONE")
... 
>>> t = Thread(target=foo, args=[])
>>> t.start()
>>> DONE

>>> 

Jim Newton14:01:57

I asked on Python discord, and nobody suggested that. I searched a bit on stackexchange and found several interfaces, which didn't seem to work recursively. I played with it for a few hours and finally gave up. Someone gave me so code that works, but I don't understand it.

Jim Newton14:01:43

Someone claimed that nothing as easy as future in clojure exists in python because the model is very different, and doesnt assume the jvm.

Jim Newton14:01:00

does future work so easily in JS clojure?

delaguardo14:01:57

it is unavailable in cljs due to singlethreaded nature of JS engines

😥 1
Jim Newton14:01:26

Here is the python code I'll show my students. I'm a bit nervous because I don't really understand it.

import asyncio


async def sum_list(data, op):
    if len(data) == 1:
        print(f"singleton: {data[0]=}")
        return data[0]
    else:
        print(f"summing: {len(data)} {data=}")
        mid = len(data) // 2
        computed = await asyncio.gather(sum_list(data[0:mid], op),
                                        sum_list(data[mid:], op))
        return op(computed[0], computed[1])


if __name__ == "__main__":
    print(asyncio.run(sum_list(range(12), lambda a,b: a+b)))

delaguardo14:01:07

> Someone claimed that nothing as easy as future in clojure exists in python because the model is very different, and doesnt assume the jvm. I don't know about what model they are talking about but in clojure future creates a thread and runs some function in it. Because python has threads and HOFs it can do just the same

Jim Newton14:01:54

some of the examples I tried from stackexchange forced the programmer to set up a thread manager object (forget the actual term used), but it was not clear whether I could recursively create such objects, and whether I could create threads within threads within threads. In my experimentation I got what looked to me like deadlocks, which I never figured out.

Jim Newton14:01:25

is seems to me that there should be something as easy. but clojure's model is d*mn easy.

Jim Newton14:01:12

model: just call future and dereference it sometime later with @

Jim Newton14:01:26

and let the language manage the queue

Jim Newton14:01:54

another problem was that the python examples used side effects.. it wasn't clear how to manage return values. that probably would not have been difficult to figure out, except that i was having deadlocks for no obvious reason

Jim Newton14:01:00

question: some of the python examples made the programmer care about how many cpus are available. Is that something the programmer should be concerned about?

Jim Newton14:01:49

in my opinion, even if i have several cpus, I don't know whether other users or other programms (such as my IDE) are using them, and if I'm calling a function I didn't write, I don't know who many cpus that function is going to try to use.

Jim Newton13:01:45

what's the best way to test whether a sequence is a singleton? counting might fail if it is infinite, and taking the first element might fail because the first element might be false.

dpsutton13:01:38

(= 1 (bounded-count 2 coll))

1
👍 2
dpsutton13:01:00

(defn bounded-count
  "If coll is counted? returns its count, else will count at most the first n
  elements of coll using its seq"
  {:added "1.9"}
  [n coll]
  (if (counted? coll)
    (count coll)
    (loop [i 0 s (seq coll)]
      (if (and s (< i n))
        (recur (inc i) (next s))
        i))))

1
dpsutton13:01:19

does what you want. If it can O(1) get the count, use that, otherwise iterate through it with a bound

1
dpsutton13:01:46

(note this is in clojure.core not just a useful helper function you could add)

1
Jason Bullers13:01:04

I'm guessing this performs better than something like:

(= 1 (count (take 2 coll)))

Jason Bullers13:01:26

Or is there some other reason that wouldn't work?

delaguardo13:01:38

bounded-count does not create extra seq

👍 1
dpsutton13:01:28

@U04RG9F8UJZ that would be a fine way to do it but it is missing the obvious trick: if something knows it’s count (like a vector, a string, etc) we don’t have to iterate through it, we can just grab the number

👍 1
1
Ryan22:01:06

Hey all, so I think this may be a a reduce fn, but how would you combine a vector of 2 vectors

[[:a :b] [:c :d]] [[:e :f] [:g :h]] such that the result was [:a ::b :e :f] [:c :d :g :h]
… can be up to a few dozen vector vectors```

Ryan22:01:43

added colon to ::b to avoid emoticon replacement :)

Bob B22:01:42

you could probably do reduce with into

Bob B22:01:10

oh wait, I misread that... map into maybe

Samuel Ludwig22:01:32

could use (juxt first second) to split up the pairs, and then whenever you mash them back together you can use flatten/`flatmap`?

Samuel Ludwig22:01:47

ok, so even better

(->> my-vec (apply interleave) (partition-all 2))

Sam Ferrell22:01:48

(let [a [[:a :b] [:c :d]]
      b [[:e :f] [:g :h]]
      c [[:i :j] [:k :l]]]
  (into [] (comp (partition-all 3) (map flatten))
        (interleave a b c)))
;; [(:a :b :e :f :i :j) (:c :d :g :h :k :l)]
Something similar

Samuel Ludwig22:01:21

the timing 😄

Ryan22:01:37

Thanks for all the advice, I knew there were some core fns that could make this process less brain-bending

Samuel Ludwig22:01:10

my solution actually needs one more step

(->> my-vec
     (apply interleave)
     (partition-all 2)
     (map flatten))

Ryan22:01:47

Yeah wow that does the trick

Ryan22:01:15

Thanks SO much!

Samuel Ludwig22:01:45

:^) 🤙, got you, benefit of preferring to have '100 functions that work on 1 datastructure' over '10 functions that work on 10 datastructures': we got all the tools in the world to manipulate sequences simple_smile

🙏 1
Ryan22:01:44

We need tools this power to operate in this cruel world! lol

Ryan22:01:10

Working with an API with PersonID and LocationID, and since Person 1:N Locations, you need to repeat the PersonID to positionally match the second argument, e.g. ?person-id=1,1,2,2,3,4,5&location-id=1,2,3,4,5,6,7 … they are monsters… that oughta be illegal lol

1
🤢 1
Ryan23:01:39

I mean on the bright side I can unironically say I actually needed to use (take n (repeat x))…

delaguardo23:01:54

map can take any number of arguments

(map concat [[:a :b] [:c :d]] [[:e :f] [:g :h]])
;; => ((:a :b :e :f) (:c :d :g :h))

🙌 3
seancorfield23:01:46

Ah, he beat me to it!

user=> (let [x [[[:a :b] [:c :d]] [[:e :f] [:g :h]]]] (apply map concat x))
((:a :b :e :f) (:c :d :g :h))