Fork me on GitHub
#cljs-dev
<
2016-02-06
>
mfikes14:02:24

I had asked in the main channel about

(defn f [^boolean b]
  (loop [x b]
    (if x
      (recur 0)
      :done)))
returning for (f true). I partially expected that type hints would only propagate to places where it is statically known to be valid, and that loop isn’t one (as opposed to let). At this point, either the code above is “incorrect, malformed”, or there is something in my understanding that is off, or there is a compiler bug (or any of the three.)

meow14:02:59

How is loop any different than let in terms of value binding?

mfikes14:02:36

I suppose, at least in the example above, if that were a let then you could, via static analysis, conclude that x holds a Boolean value, whereas with loop you can’t do that owing to the fact that recur can result in x containing a different type of value (number in, the example).

meow14:02:25

And you're seeing the type hint propogate to x?

mfikes14:02:29

My thinking is roughly: To determine the static type of a bound local, you’d have to analyze all the places where it can be set. And let vs. loop seem to differ in this regard.

mfikes14:02:31

Exactly, ^boolean propagates to x, and that is the core of my question: Whether this propagation is overly aggressive (unsafe), without further analysis.

meow14:02:49

Does the type hint remain constant through each iteration of the recur?

mfikes14:02:58

The alternative is “that’s just the way type propagation works, and the program I wrote in the example is simply malformed or incorrect”.

meow14:02:48

Or are type hints only assigned at compile time?

meow14:02:26

Or is it really a new var each time through the loop?

mfikes14:02:14

It is a static thing, affecting the emitted JavaScript in a static fashion. (See http://blog.fikesfarm.com/posts/2015-12-04-boolean-type-hints-in-clojurescript.html)

meow14:02:31

Okay. Well, since this is a cljs issue I see this as a compiler bug.

meow14:02:32

Conceptually it doesn't feel right because of the dynamic nature of the implied binding in the loop, but ultimately the resulting js code is all that matters here.

meow14:02:15

So, don't use loop/reccur because I don't like them - use for, into, or anything else. 😉

mfikes14:02:36

Always shoot for (- 100 10), my friend. simple_smile

meow14:02:27

No need to go any further.

bronsa15:02:31

@mfikes: the clojure compiler does type propagation in loops

bronsa15:02:20

in (loop [x 1] (recur "foo")) x is Object, in (loop [x 1] (recur (inc x))) x is long

mfikes15:02:20

@bronsa: That’s what I suspected. Perhaps ClojureScript could do the same kind of analysis.

dnolen15:02:12

@mfikes: ah I see the conversation got moved here - yeah confirming that the types match would be right thing to do

dnolen15:02:25

that code should emit a warning for sure

mfikes15:02:38

Or, should it conclude that x doesn’t have a static type?

mfikes15:02:55

But, I see what you are saying @dnolen : Type hint propagation is going to occur here, regardless of what is in recur forms, so the code is invalid, and you are suggesting that a WARN is in order.

dnolen15:02:21

@mfikes: the problem is that the type is inferred to be boolean

dnolen15:02:39

and then the user turns around and binds number

mfikes15:02:47

Right. Clojure seems to “deopt” that case by looking at the recur form.

mfikes16:02:05

Captured the above with an enhancement ticket: http://dev.clojure.org/jira/browse/CLJS-1561

mfikes21:02:15

Attached a patch that results in

cljs.user=> (defn f [^boolean b]
       #_=>   (loop [x b]
       #_=>     (if x
       #_=>       (recur 0)
       #_=>       :done)))
WARNING: recur target parameter x has inferred type boolean, but being passed type number at line 4 

bronsa21:02:33

@mfikes: is compilation affected or is just a warning emmitted? in CLJ mismatched recur types actually affect compilation

mfikes21:02:01

@bronsa: Just a warning that can even be disabled by users

bronsa21:02:32

I guess what I'm asking is, will the CLJS compiler deoptimize that case or emit a warning and compile to unsafe code that will break on (recur 0)?

mfikes21:02:08

It will emit a warning and compile to unsafe code

mfikes21:02:35

(I’m actually in favor of deoptimizing. Following David’s lead.)

bronsa21:02:37

@mfikes @dnolen just FYI, in case aligning with clj (deoptimizing rather than just emitting a warning), becomes an option the CLJ compiler does this by recursively re-macroexpanding/analyzing the macro bodies until no mismatched recur locals are present

bronsa21:02:26

(which was a pain to implement in tools.analyzer btw)

mfikes21:02:46

From what little experience I have with this particular case, the warning appeared to be relatively easy to implement, while deoptimizing, on the surface, might be more difficult.

meow21:02:55

You can do it, Mike. Just do (- 100 10) and call it good enough.