Fork me on GitHub
#beginners
<
2018-10-29
>
Eccentric J03:10:02

Less code focused question: So I’m working through brave and true but I’m currently stuck on Exercise #7.2 requesting I make an infix macro that obeys the order of operations (infix ‘(1 + 3 * 4 - 5)). On one hand I get that I need to recursively pair off the priority calculations into lists of 3 then nest them so they properly evaluate to 8. On the other hand I’m not entirely sure how to implement it. Do I struggle with it until I come up with a solution no matter how long it takes or do I find an existing solution and make sure I understand how to solve it? I’m interested in what’s the recommended path for learning in situations like this.

valerauko03:10:22

that depends on you. i usually try to solve it myself but once it gets frustrating i just find a solution and dissect it until i understand it

andy.fingerhut03:10:14

Another intermediate possibility: after thinking about it for a while and not yet coming up with a solution, you can ask for a hint that hopefully doesn't give away the whole answer, which of course might vary in quality depending upon who answers.

Michael Fiano04:10:30

Pointless question: Is there anything like as-> but which functions either like -> or ->> when the binding for a form isn't specified?

seancorfield04:10:51

@mfiano Not quite sure what you're asking there... as-> without a binding is pretty much -> by definition isn't it? What am I missing?

Michael Fiano04:10:40

With the following, form2 doesn't specify the binding. I would like something that either behaves like thread-first or thread-last in this case. It doesn't matter which, as long as it is defined behavior.

(as-> foo m
  (form1 x m y)
  (form2 x y))

Michael Fiano04:10:37

The answer seems to be the swiss-arrows library. As per the documentation: If no <> position marker is found in a form within the Diamond Wand -<>, the default positioning behavior follows that of the -> macro. Likewise, if no position is specified in a form within the Diamond Spear -<>>, the default is has the positioning semantics of ->>

seancorfield04:10:35

That sounds unnecessarily magic and a bad idea.

seancorfield04:10:49

For a start, as-> is really only meant to be used inside a -> pipeline.

seancorfield04:10:19

So you'd have

(-> foo
  (as-> m
    (form1 x m y))
  (form2 x y))

Michael Fiano04:10:13

And what if I have a ->> pipeline with 1 or more forms requiring threading through the first argument?

Wagk04:10:46

Hello, does anyone know why (* []) returns [] and not throw some error?

seancorfield04:10:46

Start with -> and then you can thread into ->>

Michael Fiano04:10:14

@polkafreaku It does indeed error for me. What version are you using?

seancorfield04:10:25

(* []) => (cast Number [])

Wagk04:10:43

I'm running lumo, specifically `

Wagk04:10:49

Lumo 1.9.0-alpha
ClojureScript 1.10.238
Node.js v9.10.0

Wagk04:10:01

so it's a js thing I guess?

seancorfield04:10:02

It probably doesn't do the cast

seancorfield04:10:14

See what (source *) shows you.

seancorfield04:10:38

(assuming lumo works like a Clojure REPL, which I don't know)

Wagk04:10:53

cljs.user=> (source *)
Evaluating (source *)
lumo.repl.source_STAR_.call(null,new cljs.core.Symbol(null,"*","*",(345799209),null))
(defn ^number *
  "Returns the product of nums. (*) returns 1."
  ([] 1)
  ([x] x)
  ([x y] (cljs.core/* x y))
  ([x y & more] (reduce * (cljs.core/* x y) more)))
nil

Wagk04:10:41

should I raise a bug or something?

seancorfield04:10:36

I don't think cljs supports cast -- because of JS.

seancorfield04:10:44

Not a bug, just a quirk of the platform.

andy.fingerhut04:10:57

But you are likely to get answers from people with much more intimate knowledge of ClojureScript on the #clojurescript channel, FYI. (No disrespect to Sean)

andy.fingerhut04:10:20

when it gets down into the nitty gritty details of error cases like this, I am pretty sure there are lots of tiny differences.

seancorfield04:10:36

It's also an underlying principle of Clojure (and ClojureScript) that, in general, bad inputs are not checked for -- for performance reasons. This is sometimes called "Garbage In, Garbage Out" 🙂

seancorfield04:10:16

I'm kinda surprised the Clojure version of * actually casts the argument... The cljs source is what I actually expected the clj version to be

seancorfield04:10:00

(yup, I claim no expertise with cljs -- and I'm assuming it doesn't have cast hence the difference here)

andy.fingerhut04:10:20

Probably was not considered much of a performance issue for Clojure/Java to do the cast for the 1-arg case, given how unusual the 1-arg case occurs in most code.

andy.fingerhut04:10:59

But yeah, I was also a little surprised to see the cast there in Clojure/Java.

seancorfield04:10:29

Am I right that cljs doesn't support cast BTW?

seancorfield04:10:54

(I've almost never seen it used in Clojure so I was a bit surprised it was even a thing there!)

Wagk04:10:21

it appears at least lumo doesn't

Michael Fiano04:10:05

Th "garbage in, garbage out" philosophy often leads to "accidental errors" as Eric Normand puts it, but it will be much more tolerable once core functions are fully spec'd from what I gathered.

andy.fingerhut04:10:19

And people have done their own personally spec'd versions for core functions to implement the arg checking they prefer, just nothing official or widely used that I know about there.

andy.fingerhut05:10:04

I've released a library that doesn't use spec, but function preconditions, to throw exceptions if you call a clojure.set function with non-set args (or whatever type is appropriate for the function), which is significantly faster than spec instrument, as the README shows measurements for. Either people don't know about it, or know about it but don't care to turn on the extra checking, or they are doing it themselves if they want it without the lib: https://github.com/jafingerhut/funjible

Michael Fiano05:10:10

I played with spec quite a bit when I was learning Clojure, but I haven't made use of it yet for a serious project. I'm actually writing my second serious project right now, and the API is very unstable still. It's turning out to be quite non-trivial, so hopefully it won't be too difficult to spec once I flesh things out completely.

Michael Fiano05:10:40

I don't use Slack much, but is it intended that I'm able to edit this channel's topic when I click on it?

seancorfield05:10:41

Yeah, because it's designed for teams -- expected to cooperate -- not for giant communities like this 🙂

Michael Fiano05:10:55

Ha fair enough.

seancorfield05:10:30

As an Admin, I sure wish there were more permission controls on many aspects of Slack but, hey... we get what we pay for 🙂

Michael Fiano05:10:12

There's always other services like Matrix these days, but migration is the issue of course.

seancorfield05:10:42

There's #community-development for those discussions 🙂

seancorfield05:10:08

(and, yes, there are lots of other Clojure communities online, but they're all pretty small by comparison)

sb10:10:20

possible create with clojure.zip in order or post order traversal?

sb10:10:05

pre order traversal is z/next as I see

schmee10:10:11

is it a requirement to use clojure.zip? otherwise I highly recommend Specter for navigating recursive data structures

sb10:10:01

No, not requirements. I just want to understand where can I use zippers, what is the limits. I check Specter now, thanks!

sb10:10:53

@schmee thanks!! 👍

Johnap1912:10:47

Hello,to all in here.

TK14:10:23

Hello people, I have a question on best practices + pure functions.. Imagine I have a big function f. Now I start to refactor and separate it in a bunch of smaller functions. Let's say now I have 5 smaller functions. f calls f1, that calls f2, and so on... Imagine f4 needs to use data (variable) from f function. Whats is the best practice to f4 use the data? Do I need to pass the data from f to f1 to f2 to f3 to f4? Or there any other solution to this? I'm trying to make all my function pure...

chrisulloa14:10:49

@leandrotk100 Is there any part of the big function f that is impure?

orbaruk14:10:12

I would probably rewrite f to use a thread macro to call and redirect the imputs from all de other fn

chrisulloa14:10:43

Doesn’t seem like it’s a question of the purity of function, but more like how should you structure your code. Break out the individual pieces you want to test, then compose them to make the bigger f. Using comp or threading like orbaruk suggests is probably the best way to go.

👍 1
TK14:10:53

@christian.gonzalez There are any benefits on using comp instead of threading and vice-versa?

chrisulloa15:10:57

I find threading to be cleaner and easier to read @leandrotk100. I’d say it’s a more data oriented approach whereas comp is a more functional approach.

chrisulloa15:10:21

Also with comp you end up needing to use partial quite a bit

frenata15:10:00

comp and partial, friends forever

frenata15:10:03

@leandrotk100 it's also worth knowing that -> and friends are macros and thus operate at read time rather than eval time.

valerauko16:10:29

when you say "needs to use data (variable) from f function" does that mean there's a part of the original f not covered by any of the numbered ones?

dpsutton16:10:15

maybe a gist would clear up some ambiguities and allow more help? seems pretty vague at this time

jaawerth16:10:15

yeah it sounds like the first function has some value needed in the fourth and not any intermediate ones. This definitely happens from time to time in composed functions or transducer pipelines and you pretty much have to either pass it on through each or break down the immaculate pipeline and partially apply whatever it is to f4. there are worse things in the world than not having a pretty (-> data f1 f2 f3 f4) 😉

manutter5116:10:48

One thing I do sometimes is to package the function args up into a map and just thread the map through.

(defn f1 [{:keys [everybody-needs-me f1-needs-me] :as opts}]
   ;; do something with those args
  (f2 (assoc opts :f2-needs-me :some-val)))

(defn f2 [{:keys [everybody-needs-me f2-needs-me] :as opts}]
  ;; do something with those args
  (f3 (dissoc opts :f1-needs-me :f2-needs-me)))

(defn f3 [{:keys [everybody-needs-me] :as opts}]
  ;; etc
  (f4 opts))

(defn f4 [{:keys [everybody-needs-me f4-needs-me] :as opts}]
  ;; etc
  )

(f1 {:everybody-needs-me 1
     :f1-needs-me 2
     :f3-needs-me 3
     :f4-needs-me 4})

manutter5116:10:14

eh, those functions need to be defined in the reverse order, but you get the idea.

hiredman17:10:16

something like https://gist.github.com/hiredman/71b71e4e07e7666fe1b9def9a476c765 (minus all the mapcat stuff) might be useful for refactoring that kind of thing, you would say what data each function depends on and let something else determine what order they need to be called in

Mario C.18:10:04

Question about cacheing: Is there anyway to cache a function that receives a map and have it ignore a certain key in it?

Mario C.18:10:13

such as a UUID

noisesmith18:10:25

you could compose select-keys with a cached function

Mario C.18:10:53

hmm could I have an example that would illustrate the point?

noisesmith18:10:09

user=> (def frob (memoize (fn [x] (println x) (vals x))))
#'user/frob
user=> (def f (comp frob #(select-keys % [:a :b :c])))
#'user/f
user=> (f {:a 0 :b 1 :c 3 :uuid (java.util.UUID/randomUUID)})
{:a 0, :b 1, :c 3}
(0 1 3)
user=> (f {:a 0 :b 1 :c 3 :uuid (java.util.UUID/randomUUID)})
(0 1 3)
user=> 

👍 1
noisesmith18:10:41

if the cached function doesn't look at a key, just remove it before the function call

Michael Fiano18:10:21

I have this function:

(defn- parse-page-segments
  [file]
  (let [segment-count (types/read :uint file)
        segment-len (->> (repeatedly #(types/read :uint file))
                         (take segment-count))]
    (into {} (map-indexed vector segment-len))))
types/read is reading a byte from a file stream each time it is called. Now everything works if I leave off the into there, but with it it causes the pipeline to be executed multiple times if segment-count is > 1. I'm completely baffled as to why this only occurs by converting the sequence of vectors into a map.

noisesmith18:10:04

glad I could help

noisesmith18:10:42

@mfiano which part of this do you call "the pipeline"

Michael Fiano18:10:57

Thread last form

noisesmith18:10:45

so the number of times types/read is called is some multiple of segment-count?

noisesmith18:10:03

the big difference is that into as used there makes the call eager instead of lazy

noisesmith18:10:31

if you wrap the map-indexed call in doall, does this make it behave like the into version did?

Michael Fiano18:10:03

I get garbage data due to too many byte reads when i use into like above, but without it works as expected

noisesmith18:10:28

what about with doall?

Michael Fiano18:10:35

I can try that in a few. On mobile right now

Mario C.18:10:40

@noisesmith Hmm what if the function does need to look at the key?

noisesmith18:10:53

but how would you correctly memoize it?

noisesmith18:10:31

you could decompose the logic inside the function, and memoize the part that doesn't use that key, but there's no way to memoize something as if it didn't look at a key, and also use the key...

Mario C.18:10:39

I see and that makes sense, thanks again

Michael Fiano18:10:14

@noisesmith Yes. Using doall in place of into causes the same thing with garbage data.

noisesmith18:10:34

so the real problem is that the number provided to take is wrong I bet

Michael Fiano18:10:09

That does not seem to be the case. Without into I get the same values as my existing parser in Common Lisp.

noisesmith18:10:54

but all doall and into are doing differently is changing when a value is consumed

noisesmith18:10:01

perhaps there's a race condition?

Michael Fiano18:10:46

This is not threaded, and both doall and into have the problem.

noisesmith18:10:03

what they have in common is making a value non lazy

noisesmith18:10:20

which means changing the timing of realizing results

noisesmith18:10:35

there's literally nothing else doall does - that's its only job

noisesmith18:10:30

you're certain there's no thread context switching in eg. the underlying IO?

Michael Fiano18:10:26

I'm not certain. I'm just using on a path string.

noisesmith18:10:05

unless there's another very weird thing going on that you haven't described yet, the only reason that doall would change the correctness of a result is a hidden thread synchronization issue

Michael Fiano18:10:15

Let me try to produce an isolated test

noisesmith18:10:19

the common thing is someone opening a input source in some lexical context, and getting a closed input error, that goes away when using doall

noisesmith18:10:08

so you could have the reverse - doall reading "extra" results, and in the other case the source is closed getting the results expected

andy.fingerhut18:10:49

Could the take call be doing 1 extra evaluation of its input than the minimum required? That occurs with some sequence functions in some situations, I believe. Laziness and side effects, where you want to guarantee absolutely minimum evaluation, are something I wouldn't recommend, because of these behaviors.

noisesmith19:10:29

this could be a case for using eduction with the take transducer

noisesmith19:10:42

agreed that laziness and IO are an iffy combo

andy.fingerhut19:10:20

Personally, I would reach for loop with reading/writing side effects inside of it, if you want to control how many of them occur, and when.

noisesmith19:10:18

IIRC, eduction was designed for cases where you want nice functional things like take, map, filter, in a side-effecting context

noisesmith19:10:51

(but yes, loop would work here)

noisesmith19:10:35

correcting myself above: eduction is meant for contexts where transducers aren't currently available, which can include IO, but also includes composing with various other functions that don't integrate with transducers

Michael Fiano19:10:21

Well I surely cannot reproduce it in an isolated test:

(defn test1
  [path]
  (let [stream ( path)
        bytes (->> (repeatedly #(.read stream))
                   (take 10))]
    (map-indexed vector bytes)))

(defn test2
  [path]
  (let [stream ( path)
        bytes (->> (repeatedly #(.read stream))
                   (take 10))]
    (into [] (map-indexed vector bytes))))

;;;

user> (test1 "/tmp/test.bin")
;; => ([0 79] [1 103] [2 103] [3 83] [4 0] [5 2] [6 0] [7 0] [8 0] [9 0])
user> (test2 "/tmp/test.bin")
;; => [[0 79] [1 103] [2 103] [3 83] [4 0] [5 2] [6 0] [7 0] [8 0] [9 0]]

andy.fingerhut19:10:27

You can choose to fight the battles you want -- in Clojure, I personally would avoid the laziness+side effects debug battle.

hiredman19:10:54

Mixing lazy seqs with io is often problematic, I wouldn't be surprised if the doc string for repeatedly doesn't specify the function should be side effect free

noisesmith19:10:50

also, following repeatedly by take is silly because repeatedly takes an explicit "limit" arg

hiredman19:10:55

(w/o the double negative)

seancorfield19:10:47

repeatedly is intended for side-effecting functions per the doc string "Takes a function of no args, presumably with side effects, ..."

noisesmith19:10:50

it explicitly mentions "presumably with side effects" though

hiredman19:10:27

Oh, right, I guess it would be

seancorfield19:10:45

@mfiano and I had a brief DM about this earlier and my feeling is this is due to something higher up his call chain -- not in parse-page-segments. Which is why the isolated test works (and matches what I'd been trying to test in the REPL).

noisesmith19:10:31

it could be in the ByteBuffer usage, since that involves mutations that wouldn't be thread safe - but I

noisesmith19:10:44

only mention that because it's the other thing I saw mentioned about this code

noisesmith19:10:50

it could easily be anything else

andy.fingerhut19:10:58

Why I say what I said above -- there have been N tickets filed against Clojure/Java on "function X evaluates lazy sequences once/twice/M times more than the minimum it could", where N is somewhere in the range 10 to 15, I think (over the last 10 years or so). Many have led to changes in Clojure, likely some have not and the behavior remains.

andy.fingerhut19:10:47

There is no solid foundation other than examining each case one by one to base "this code will not evaluate any more than the minimum necessary of a lazy sequence"

andy.fingerhut19:10:41

Stated another way: "If you are a masochist, I think you will enjoy mixing laziness and side effects in Clojure."

Michael Fiano21:10:17

The problem was an off by one error in my byte reading that caused the side-effecting lazy sequence to read bad data and thus error from my assertion. I do agree that side-effecting lazy sequences should generally be avoided, but I think it is okay in this case since the sequence is only lazy internal to a function where it's immediately realized and thus predictable. Thanks for all the help with this. It was a red herring. Problem solved.

Ian Fernandez22:10:40

When I'm using the httpkit client to make a get in a list of addresses using for in a list, after making a try+ , catch

Ian Fernandez22:10:14

It's giving me back the requests that I make not the response

Ian Fernandez22:10:29

It's because for lazyness?

seancorfield22:10:42

@d.ian.b Perhaps show us some code?

Ian Fernandez11:10:01

(defn list-user-channels [service-sid user-id]
  (vec (let [request (:channels
                       (j/read-json
                                      (:body (when not-empty
                                               (try+ @(c/get (format "" service-sid user-id)
                                                             {:basic-auth            [account-sid api-auth]
                                                              :throw-entire-message? true})
                                                     (catch [:status 401] {:keys [request-time headers response]}
                                                       (log/fatal request-time headers response))
                                                     (catch [:status 200] {:keys [request-time headers response]}
                                                       (log/info request-time headers response)))))))] (for [channel request] (:channel_sid channel)))))

Ian Fernandez13:10:53

after I get the vec I'll interate onto these values to make a request in another api to each value