[SOLVED] UInt is not Int!

Hi !

I’ve encountered something curious, and here is a minimal example :slight_smile:

function f(x::UInt,y::UInt)::UInt
    x + y 
end

And now i want to do f(2,2) but julia says this to me :

julia> f(2,2)
ERROR: MethodError: no method matching f(::Int64, ::Int64)
Stacktrace:
 [1] top-level scope at REPL[1]:1

For a function, I need just positive numbers, no I want to force this by the typing. Is a there a way to do this ?

In some sense, why f(0x2,0x2) fails ? It says to me that there is not matching for f(::UInt8,::UInt8) … but UInt8 <: UInt is true, isn’t it ?

Thanks for answers :slight_smile:

As you’ve noticed, 2 is cast to Int, which on a 64-bit system is Int64.

0x2 is UInt8. This is NOT <: UInt, though, since UInt is UInt64 on 64-bit systems.

Just call it with UInt(...): f(UInt(2), UInt(2)), or coerce inside the function:

julia> function f(x::Integer, y::Integer)
         ux = UInt(x); uy = UInt(y)
         ux + uy
       end
f (generic function with 1 method)
       
julia> f(3, 5)
0x0000000000000008

julia> f(-3, 5)
ERROR: InexactError: check_top_bit(UInt64, -3)
Stacktrace:
 [1] throw_inexacterror(::Symbol, ::Type{UInt64}, ::Int64) at ./boot.jl:557
 [2] check_top_bit at ./boot.jl:571 [inlined]
 [3] toUInt64 at ./boot.jl:682 [inlined]
 [4] UInt64 at ./boot.jl:712 [inlined]
 [5] f(::Int64, ::Int64) at ./REPL[5]:2
 [6] top-level scope at REPL[7]:1

edited to add: I wrote a quick script that will tell me the entire type hierarchy for an object:

function typehier(x)
    z = typeof(x)
    print("$z")
    while z != Any
        z = supertype(z)
        print(" <: $z")
    end
    println
end
julia> typehier(5)
Int64 <: Signed <: Integer <: Real <: Number <: Any

julia> typehier(0x5)
UInt8 <: Unsigned <: Integer <: Real <: Number <: Any

julia> typehier("foo")
String <: AbstractString <: Any
7 Likes

Try it out:

julia> UInt8 <: UInt
false

Maybe you are looking for Unsigned instead? That’s the supertype of all unsigned integers:

julia> UInt8 <: Unsigned
true

Now even if you define f(x::Unsigned, y::Unsigned) this will still not allow you to call f(2, 2) because 2 is a Int – a signed integer. The julia way would be something like this:

f(x::Unsigned, y::Unsigned) = x + y
f(x, y) =f(convert(Unsigned, x), convert(Unsigned, y))

In other words, you define the function for Unsigned values, and you also specify that if it is passed anything else, those things should be converted to Unsigned and then passed to the original definition.

4 Likes

Ok.

1/ I’ve a problem with the number system types. I should read the doc again.

2/ Thanks for your answers to you both ! I’ve already thought to the first, but it seemed to me strange. The second is more julia-like.

Thanks for your quick answers !

Why do you want to enforce this with the type? Types in the function signature are great for multiple dispatch, but I think a more common pattern for argument checking is something like

function f(x, y)
    x <= 0 && error("x should be positive.")
    y <= 0 && error("y should be positive.")
    x + y
end

See e.g. sqrt in base/math.jl

@inline function sqrt(x::Union{Float32,Float64})
    x < zero(x) && throw_complex_domainerror(:sqrt, x)
    sqrt_llvm(x)
end
2 Likes

Note that the generic error function isn’t a good fit here since it obscures the actual error in a generic message. Contrast that with the sqrt function, which properly throws a DomainError:

julia> using Test

julia> @test_throws DomainError sqrt(-1)
Test Passed
      Thrown: DomainError

which is much easier to explicitly catch and handle. If you want to restrict the function, you can first check the arguments, convert afterwards and work with the converted arguments to get the maximum out of the compiler.

1 Like

Because I have a complex type system ? It should be unnice if I couldn’t use it for easing my code :slight_smile:

Having a complex type system does not mean you have to or should use it for everything. In fact, using it to restrict user input is almost always bad. The user will have a MUCH better time understanding the error message from sqrt(-1) than from your function.

3 Likes

If you don’t want to, don’t put types on your function signatures. It’s not required or even helpful for performance. You can write Julia code without types just like you would Python, R or Matlab.

4 Likes

[!Warning Pendantic!] Yes but isn’t there a caveat that if you leave out types from structs and data objects altogether it will lead to a degradation in performance where the compiler starts substituting Any? For example this:

struct MyTypedStruct{T}
  x::T
end

Compared with this:

struct MyUnTypedStruct
x
end

The second implementation will give Any warnings when passed into @code_warntype:

function fun(x)
  return x.x^2 + 2
end
@code_warntype fun(MyTypedStruct(3.0))

Creates good code:

Variables
  #self#::Core.Compiler.Const(fun, false)
  x::MyTypedStruct{Float64}

Body::Float64
1 ─ %1 = Base.getproperty(x, :x)::Float64
│   %2 = Core.apply_type(Base.Val, 2)::Core.Compiler.Const(Val{2}, false)
│   %3 = (%2)()::Core.Compiler.Const(Val{2}(), false)
│   %4 = Base.literal_pow(Main.:^, %1, %3)::Float64
│   %5 = (%4 + 2)::Float64
└──      return %5
@code_warntype fun(MyUnTypedStruct(3.0))

Creates code with Any type identifiers that will lead to performance penalties:

Variables
  #self#::Core.Compiler.Const(fun, false)
  x::MyUnTypedStruct

Body::Any
1 ─ %1 = Base.getproperty(x, :x)::Any
│   %2 = Core.apply_type(Base.Val, 2)::Core.Compiler.Const(Val{2}, false)
│   %3 = (%2)()::Core.Compiler.Const(Val{2}(), false)
│   %4 = Base.literal_pow(Main.:^, %1, %3)::Any
│   %5 = (%4 + 2)::Any
└──      return %5

To beginner programmers that may be reading, this might not look like much but when you use @code_llvm, the well defined type creates really good efficient small code size:

@code_llvm fun(MyTypedStruct(3.0))

;  @ REPL[3]:2 within `fun'
define double @julia_fun_17308([1 x double] addrspace(11)* nocapture nonnull readonly dereferenceable(8)) {
top:
; ┌ @ Base.jl:33 within `getproperty'
   %1 = getelementptr inbounds [1 x double], [1 x double] addrspace(11)* %0, i64 0, i64 0
; └
; ┌ @ intfuncs.jl:261 within `literal_pow'
; │┌ @ float.jl:405 within `*'
    %2 = load double, double addrspace(11)* %1, align 8
    %3 = fmul double %2, %2
; └└
; ┌ @ promotion.jl:311 within `+' @ float.jl:401
   %4 = fadd double %3, 2.000000e+00
; └
  ret double %4
}

whereas untyped code creates a horribly inefficient monstrosity:

@code_llvm fun(MyUnTypedStruct(3.0))

;  @ REPL[3]:2 within `fun'
define nonnull %jl_value_t addrspace(10)* @japi1_fun_17309(%jl_value_t addrspace(10)*, %jl_value_t addrspace(10)**, i32) #0 {
top:
  %3 = alloca %jl_value_t addrspace(10)*, i32 3
  %gcframe = alloca %jl_value_t addrspace(10)*, i32 3, align 16
  %4 = bitcast %jl_value_t addrspace(10)** %gcframe to i8*
  call void @llvm.memset.p0i8.i32(i8* align 16 %4, i8 0, i32 24, i1 false)
  %5 = alloca %jl_value_t addrspace(10)**, align 8
  store volatile %jl_value_t addrspace(10)** %1, %jl_value_t addrspace(10)*** %5, align 8
  %thread_ptr = call i8* asm "movq %fs:0, $0", "=r"()
  %ptls_i8 = getelementptr i8, i8* %thread_ptr, i64 -15712
  %ptls = bitcast i8* %ptls_i8 to %jl_value_t***
  %6 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 0
  %7 = bitcast %jl_value_t addrspace(10)** %6 to i64*
  store i64 4, i64* %7
  %8 = getelementptr %jl_value_t**, %jl_value_t*** %ptls, i32 0
  %9 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 1
  %10 = bitcast %jl_value_t addrspace(10)** %9 to %jl_value_t***
  %11 = load %jl_value_t**, %jl_value_t*** %8
  store %jl_value_t** %11, %jl_value_t*** %10
  %12 = bitcast %jl_value_t*** %8 to %jl_value_t addrspace(10)***
  store %jl_value_t addrspace(10)** %gcframe, %jl_value_t addrspace(10)*** %12
  %13 = load %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %1, align 8
; ┌ @ Base.jl:33 within `getproperty'
   %14 = addrspacecast %jl_value_t addrspace(10)* %13 to %jl_value_t addrspace(11)*
   %15 = bitcast %jl_value_t addrspace(11)* %14 to %jl_value_t addrspace(10)* addrspace(11)*
   %16 = load %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)* addrspace(11)* %15, align 8
; └
  %17 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %3, i32 0
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140357750891408 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %17
  %18 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %3, i32 1
  store %jl_value_t addrspace(10)* %16, %jl_value_t addrspace(10)** %18
  %19 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %3, i32 2
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140357711681584 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %19
  %20 = call nonnull %jl_value_t addrspace(10)* @jl_apply_generic(%jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140357755649744 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %3, i32 3)
  %21 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 2
  store %jl_value_t addrspace(10)* %20, %jl_value_t addrspace(10)** %21
  %22 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %3, i32 0
  store %jl_value_t addrspace(10)* %20, %jl_value_t addrspace(10)** %22
  %23 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %3, i32 1
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140357616279712 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %23
  %24 = call nonnull %jl_value_t addrspace(10)* @jl_apply_generic(%jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140357742243008 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %3, i32 2)
  %25 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 1
  %26 = load %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %25
  %27 = getelementptr %jl_value_t**, %jl_value_t*** %ptls, i32 0
  %28 = bitcast %jl_value_t*** %27 to %jl_value_t addrspace(10)**
  store %jl_value_t addrspace(10)* %26, %jl_value_t addrspace(10)** %28
  ret %jl_value_t addrspace(10)* %24
}

One of Julia’s selling points is performance and another is the ability to write code as if you were doing so using a static programming language with all the benefits of a dynamic language so it shouldn’t come as a surprise that people want to use the type system and get all the performance benefits. My personal feeling is that there’s nothing wrong with using types it constrains the code your intent and if all your Julia code is typed you avoid performance penalties like the one described above.

1 Like

Sure, but no worse than those languages.

2 Likes

Yes, but Julia is better. Having a type system in a dynamic language that creates efficient code is mind-blowing :exploding_head:. You couldn’t write something like this in any other language; Julia parser and writer for the IDX file format used by e.g. the MNIST dataset · GitHub just explicit and simple. With other languages you’ve have to glue languages together or maybe resort to some kind of horrible polymorphic kludge.

Right, of course, there are many things you can’t do in those languages that you can do in Julia because of the type system. But the point I’m trying to make is that we tried very hard when designing the language not to force types on people. It’s a perfectly valid modality of using Julia to forget (or not know) about types. I think sometimes newcomers may feel like they have to learn this whole type system all at once in order to use the language, but you very much do not. Don’t worry about it until you need it and then you can learn it quite incrementally—just learn what you need as you go along. You don’t have to jump into writing type heavy code right away. If you’re not defining your own types or doing low level bit twiddling, you can write a lot of useful Julia code that doesn’t mention types at all.

17 Likes

I didn’t want to cause so debate. I’ve worked a lot with OCaml, and its static typing is now in my mind, because that detects so many errors. And the first language I’ve known was C. So, wanting to write types for all my variables is an habit. Don’t take this as personal for the Julia’s creator and maintainer : Julia’s type system is awesome and really usefull, thanks :slight_smile:

1 Like