Comparing different types doesn't raise an error?!?!?

Hows this?

function bar(a::Int16)
end

function foo()
    local b::Int16 = 35

    bar(35)
end

foo()

My (naive) view is that in the case of local b::Int16 = 35 the compiler is interpreting my constant 35 as an Int16 value. However for bar(35) the compiler is saying NO the constant 35 is a Int64. Yes at their base these are two different operations, assignment vs method call. However how the constant 35 is interpreted in these two operations is different…which changes based on operation…

My naive again guess would be this happens because there may at some time in the future be another function bar which takes a numeric, in which case it wouldn’t know which one to use. Or maybe it’s just that it could happen so the compiler refuses to make guesses, even though at this point and time, there is no other option.

You are correct, however by forcing me to make the conversion (or at least looking at the function definition) I can realize that the value will be used as an Int32. Which means the calling function should operate on an Int32…which trickles up the call stack.

Know (or at least being reminded) of the size of a primitive value at the bottom of the call stack can be useful when you are at the top of the call stack and can simplify (or complicate) your input validation, especially when taking the value from a user…but you can be reasonably confident the value won’t be rejected at the end of it’s journey.

I’m not sure if the CPU pipeline handle math operations on Int32 more efficiently than int64, but the CPU cache can handle more Int32s than it can Int64s. So if the value is going to be save or used as an Int16 or an Int32 why would I manipulate it an int64? Granted I probably shouldn’t be worrying about cpu cache size, but I’d prefer my code to be closer to right than wrong from a performance standpoint. So if the value can be treated as the same primitive type in it’s whole journey that would be the most efficient path, correct?

*Converting.

No. The operations done on the 35 are different. 35 is always interpreated as 35, an Int, in both cases. Only that one of them has an implicitly inserted conversion, as do all assignments and only on assignments.

Yes? By requiring the argument to be Int32 it is by all mean forcing you to make the conversion. I still don’t see what you are complaining about. As I said, if this is the reason you choose to use ::Int32 on the argument then by all mean do it. You’ll be forced to do the conversion when you call the function exactly as you wished.

Err, so are you using the type information in your validation or not? If you are, then you are assuming the type already, if you aren’t then it’s not making any difference.

Well, if you are talking that low level, then no, passing Int32/Int64 around does not go through the memory/cache and that doesn’t make any difference.

Anyway, as I said, wanting to do operation on a known type is fine and the current behavior give you exact that. I’ll be slightly more concrete with an example. From your discription it seems that you have

struct A x::Int32 end
high_level(x, ...) = ... low_level(x) ...
low_level(a::A, x, ...) = ... a.x = x ...

Now, if you just want

Know (or at least being reminded) of the size of a primitive value at the bottom of the call stack

then it’s there already, in the declaration of A. You don’t need anything else. You’ll also get the

be reasonably confident the value won’t be rejected at the end of it’s journey.

that you wanted.

If you want to

forcing me to make the conversion

when you call low_level. Then put ::Int32 on the x on low_level. You’ll be forced to do that since you’ll get an error otherwise. (Similarly, if you want to be forced for high_level, put the assersion there instead). By all mean, you cannot expect no value being reject in this case since that’s the whole point of “forcing you to”. If by forcing you to do something you don’t mean an error if you don’t then please advice what you mean by forcing.

If you don’t want a conversion anywhere for performance reasons, then put Int32 on both functions and also make sure the input to high_level is correctly typed since this is the only way you can know for sure that no conversion on it is done within the code I show and this can propagate upward nicely as well so you can be confident that a good caller can arrange for no conversion if it’s possible. You’ll also be assured that whatever value accepted by your high_level won’t be rejected anywhere lower since it’s already of the right type.

Edit: And just to be more clear. I have no problem with the as syntax for automatic argument conversion. However, there’s no inconsistency in the current state and the usecase/concerns you’ve presented so far fits the current feature set perfectly well.

You also don’t need to explain why Int32 is more efficient than Int64, it’s not really in basically all the cases above and it does matter when you store it in a field/array in which case you explicitly state the field type you want and you get an automatic conversion.

1 Like

And sort of yes for this. In any case, taking a guess is the best way to break any consistency you’ll ever hope to have. Since you want consistency, I assume you don’t want that.

Having dug more into this (because of this thread), I’ll have to agree.

I’ll probably just grumble about this to myself. I see the point, but I also feel that it helps hide issues. As @jonathanBieler mentioned === helps alleviate some of that, but it still (for me) hides the bug. From a scripting language point of view it’s reasonable and there are times Julia feels more like scripting than programming, which I’m assuming is by design.

This I like, I also tend to like the “as” syntax for conversion, but that’s probably just from familiarity. My biggest grumble is that constants i.e. 0 doesn’t get converted to the correct type and for some reason I pass 0 around alot.

This is really just causing me to declare all the parameter types on methods which is to be expected. If at the end of the day a value is going to be of type X, then I prefer that all operations on it during the day be with it as type X.

Good =)

Yes. I won’t claim this is the only solution or the best one. It’s a good enough trade off so far.

An easier syntax for conversion could help (edit:) although in most cases just explicitly call the constructor will do. I’ve also hit my fair share of problem (although more on Float32) of constant having the wrong types. There are packages (macro) to change the type of constants in a block of code and if you compare to c++ I’d say the main thing we don’t do by default is typed varable (but we do allow it). The automatic conversion in C++ has just got me an ambiguity error at compile time yesterday and I’ve also got quite a few performance issues/compiler warnings when using the wrong literal types within an expression before assigning it to “fix the type” (actually I got one of this yesterday as well… make me wonder what did I do wrong yesterday…).

Edit: In any case, this (type of literal and maybe by extension, type of operation return value) is a deeper and more subtle topic that is much more complicated than what the title of this thread would suggest and is very context dependent so it is more likely solved via a varity of solutions. Find a coding style and using the package mentioned above are both valid (partial) solutions.

Edit2: Ah, finally find the package… GitHub - JuliaMath/ChangePrecision.jl: macro to change the default floating-point precision in Julia code

Also, for giving literals a different behavior than normal values, see Confusing difference: ^literal vs ^variable and all the linked discussions…

IMHO, it’s possible to implement. It could be useful sometimes. It breaks the insignificance of assignment to untyped variables in a benign looking case so I really don’t like it. Because of this properties, it has lead to quite a bit of confusions.

I do hope nobody took this personally. I do like Julia, and have been enjoying programming in it.

The number compared to a string issue was a coding on my part, that took me by surprise. It was in hard to reach code from a Juno standpoint: A Task in a chain of Tasks started by a GTK callback. So figuring out what was happening was tricky and I ended up having to put together a whole slew of code to setup the chain and push in dummy data so I could step through it.

2 Likes