Fork me on GitHub
#beginners
<
2018-10-09
>
peter-kehl01:10:41

I'm studying (source loop) in CLJ 1.10 (also at https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj). When its parameters involve destructuring, it puts values into vs, left side (symbols to bind) into bs:

let [vs (take-nth 2 (drop 1 bindings))
     bs (take-nth 2 bindings)]
Then it's magic. Could you help me understand the rest, please.

seancorfield02:10:53

@peter.kehl Can you be a bit more specific about which lines you don't understand? (you can link to specific lines in a file on GitHub which makes it much easier for the rest of us to follow your questions)

peter-kehl02:10:24

@seancorfield The unclarity comes from how destructure works for destructurable bindings. (destructure '[a 1 b 2]) is easy, it returns [a 1 b 2]. But what does its result mean for destructurable bindings, please?

(destructure '[[a b] [1 2] [c d] [3 4]])
 ;====>
[vec__340 [1 2] a (clojure.core/nth vec__340 0 nil) b (clojure.core/nth vec__340 1 nil) vec__343 [3 4] c (clojure.core/nth vec__343 0 nil) d (clojure.core/nth vec__343 1 nil)]

peter-kehl02:10:35

Ah, I see. vec__XXX are helper bindings to get the actual result binding composed. Thank you

peter-kehl02:10:41

After destructure, how can it be possible for some "left" sides (0-th, 2-nd, 4-th entry in the result of destructure) not to be a symbol? When you call (destructure '[:keyword-literal 1]) or (destructure '[:keyword-literal 1]), either fails.

peter-kehl02:10:37

(let [...
       bs (take-nth 2 bindings)
       ....
        bfs (reduce1 (fn [ret [b v g]]
                           (if (symbol? b)
                             (conj ret g v)
                             (conj ret g v b g)))
                         [] (map vector bs vs gs))]

seancorfield02:10:40

Interesting that it doesn't use the result of calling destructure if that returns something other than its input argument.

seancorfield02:10:04

So the bindings being used in the code above would be the original bindings, including the destructuring forms...

seancorfield02:10:43

...so those won't be symbols, e.g., (loop [a 1 {:keys [x y]} input] ...)

seancorfield02:10:06

Here a is a symbol? but {:keys [x y]} is not.

seancorfield02:10:39

so you get a binding vector that has [... g__123 input {:keys [x y]} g__123 ...] -- in other words, it delegates the actual destructuring to the expanded code.

peter-kehl02:10:35

Oh... I assumed it used result of destructure - I should have searched for db. Yes, interesting. Thank you @seancorfield

grierson15:10:22

Would this be a way of implementing If without the need for special forms.

andy.fingerhut15:10:34

In Clojure, that would evaluate both "truthy" and "falsy" first, then return one of them. It differs from Clojure's if in that way -- if only evaluates one branch, not both, which is important if there are side effects in one or both branches.

andy.fingerhut15:10:29

((oif (println "truthy") (println "falsy")) (> 2 1)) would print both "truthy" and "falsy"

mfikes15:10:38

Additionally, you’d want the falsey branch for nil

joelsanchez15:10:06

with some little adjustments I could make "oif" work, so don't give up on that...

mfikes15:10:54

The only other way I can think to approach it (in ClojureScript) involves yet another special form.

(js* "~{}?~{}:~{}" true 1  2)

jaawerth15:10:23

hahaha gross - I love it

jaawerth15:10:54

so is the idea here to avoid macros entirely?

joelsanchez15:10:45

yeah I don't get it either. map literals and defn are "special forms", aren't they

mfikes15:10:04

FWIW, using a technique like this is the way the and macro in ClojureScript can generate extremely compact / efficient JavaScript for some kinds of tests (without using if)

mfikes15:10:04

I don’t think you can build if without resorting to using a special form

jaawerth15:10:23

and I'm pretty sure all the other ones use if in some way

grierson15:10:50

What about this?

jaawerth15:10:33

clever but delay is also a macro 😄

joelsanchez15:10:09

it evals both when creating that map

mfikes15:10:35

Yeah, the deref calls are eager

jaawerth15:10:37

ah yeah you'd need to hold off the deref until later

lilactown15:10:15

you need some way to signal to the evaluator, "don't eval this bit" - which necessitates a special form

jaawerth15:10:31

at any rate, I imagine if you want to pass in a map of things that you are okay with evaluating every option, otherwise it makes more sense just to use condp and call it good

joelsanchez15:10:23

condp uses cond which is a macro. I would like to know what is allowed in this challenge

jaawerth15:10:41

yeah, I don't think there's a macroless way to do this tbh

bronsa15:10:57

there isn't

bronsa15:10:06

you need to bottom out at either a macro or a special form

mfikes15:10:14

Perhaps if you are allowed to use Java or JavaScript you could do something, but that feels like cheating

jaawerth15:10:25

@mfikes just looked at the cljs source for and btw, very clever!

bronsa15:10:26

function calls simply can't prevent evaluation the way that if needs to

jaawerth15:10:38

yeah exactly, so you need to get down into the underlying host logic of short circuiting, which needs a macro somewhere in there to call out to the other thing. Or wrap Java(Script), yeah. Unless you made all the hashmap values into functions to invoke

jaawerth15:10:54

or rather, only allowed functions in the values

joelsanchez15:10:00

I didn't know that macros were forbidden so my solution was this

(defmacro my-oif [expr a b]
  `(({true  (fn [] ~a)
      false (fn [] ~b)
      nil   (fn [] ~b)}
     ~expr)))
try it with
(my-oif (< 1 2)
        (do (println "truthy")
            :truth)
        (do (println "falsy")
            :falsy))

andy.fingerhut15:10:57

And in case anyone was wondering, there are lazy evaluation languages like Haskell where not just if statements, but everything is lazily evaluated, not strict like Clojure evaluation is.

deliciousowl16:10:41

I've always wondered, what's the advantage of using Haskell over Clojure? Which usecases, etc

andy.fingerhut16:10:03

I haven't used Haskell enough to give a good answer. Some people really prefer compile-time type checking, and a strong type system that knows the difference between pure functions and side effects, and Haskell can give you that.

andy.fingerhut17:10:17

There was a recent discussion thread I saw asking "Why do you prefer Clojure over Haskell?" that may have some clues for why someone would prefer that way. As such discussions sometimes go, there is sometimes more heat than light: https://clojureverse.org/t/why-do-you-prefer-clojure-over-haskell/1967/46

jaawerth15:10:31

maybe the no-special-forms stipulation is related to being able to partially apply/compose it, in which case there are options

noisesmith16:10:40

the thing is, I can't remember the last time I used if on a boolean arg, most of the time my "true" case is some random non-nil non-false value that also isn't strictly true

👍 4
andy.fingerhut16:10:45

You can wrap the (< 1 2) or whatever condition in (boolean (< 1 2)) to get only true or false out.

andy.fingerhut16:10:24

Or... I thought you could. Maybe not.

andy.fingerhut16:10:46

OK, I think you can, but I was temporarily thrown off by the reflection warning message in the REPL I was testing in, believing it was an exception, not just a performance warning. You can use boolean that way.

mbjarland17:10:57

i have a command line app written in clojure and built using lein. I would like to include the git revision of the git repository the tool was built from in the command line help page (something I’ve done in other languages in the past and I find it very helpful) for the tool. I.e. I would like to have programmatic access to the git revision in my project code. Essentially this is the build-time domain reaching into the project code…in my past life I would have done this via a generated class. I did a quick search and none of the leiningen plugins I found seem to do this, any thoughts? (reposting from #leiningen as that channel seems a bit slow)

hiredman17:10:52

if you are building in some build server kind of thing, usually there is some facility to pass this kind of information as environment variables in the build

hiredman17:10:00

you can also just shell out and call git

hiredman17:10:56

(clojure.java.shell/sh "git" "describe" "--tags") or something

mbjarland17:10:10

I would prefer jgit to a shell exec, but I think my main hickup is with the mechanics of source generation…though I think I just found a useful link: http://hugoduncan.org/post/generating_source_with_leiningen/

hiredman17:10:41

just write a clojure program that does what you want, then invoke that program using lein run -m

hiredman17:10:26

(you can layer all kinds of stuff with profiles and aliases on top of that to taste)

mbjarland17:10:00

ok I will noddle my way to a solution…thanks for the pointers @hiredman