Parametric typing

I have a question about parametric typing in functions. Suppose I have 2 functions as follows.

julia> f(x::Real) = x + 1
f (generic function with 1 method)

julia> g(x::T) where {T<:Real} = x + 1
g (generic function with 1 method)

They seem to behave the same to me. They both take x when x is a subtype of Real and produce the same result. What are the differences between them? In what circumstances should I use <:? Thanks!

2 Likes

This what I think about the subject:

I think that for functions with only one argument those two manners to define the type are equivalent, although the second one ( with where {T<:Real}), is more convenient when you have to use the type of the input in the body of the function. For example, the two functions produce the exact same machine code:

julia> function f(x::Real)
       y = zero(eltype(x))
       for i=1:div(x,2)
         y += i
       end
       end
f (generic function with 1 method)

julia> function g(x::T) where T<:Real
       y = zero(T)
       for i=1:div(x,2)
         y += i
       end
       end
g (generic function with 1 method)

julia> @code_native f(10)
	.section	__TEXT,__text,regular,pure_instructions
Filename: REPL[1]
	pushq	%rbp
	movq	%rsp, %rbp
Source line: 4
	popq	%rbp
	retq
	nopw	%cs:(%rax,%rax)

julia> @code_native g(10)
	.section	__TEXT,__text,regular,pure_instructions
Filename: REPL[2]
	pushq	%rbp
	movq	%rsp, %rbp
Source line: 4
	popq	%rbp
	retq
	nopw	%cs:(%rax,%rax)

julia> @code_native f(10.)
	.section	__TEXT,__text,regular,pure_instructions
Filename: REPL[1]
	pushq	%rbp
	movq	%rsp, %rbp
Source line: 3
	subq	$64, %rsp
	vmovsd	%xmm0, -8(%rbp)
	movabsq	$4665565568, %rax       ## imm = 0x11616E180
	vmovsd	(%rax), %xmm1           ## xmm1 = mem[0],zero
	movabsq	$fmod, %rax
	callq	*%rax
	vmovsd	-8(%rbp), %xmm1         ## xmm1 = mem[0],zero
	vsubsd	%xmm0, %xmm1, %xmm0
	movabsq	$4665565576, %rax       ## imm = 0x11616E188
	vmulsd	(%rax), %xmm0, %xmm0
	vroundsd	$4, %xmm0, %xmm0, %xmm2
	movabsq	$colon, %rax
	movabsq	$4665565584, %rcx       ## imm = 0x11616E190
	vmovsd	(%rcx), %xmm0           ## xmm0 = mem[0],zero
	leaq	-56(%rbp), %rdi
	vmovapd	%xmm0, %xmm1
	callq	*%rax
	xorl	%eax, %eax
	movq	-24(%rbp), %rcx
	nopl	(%rax)
L112:
	addq	$1, %rax
	cmpq	%rax, %rcx
	jge	L112
Source line: 4
	addq	$64, %rsp
	popq	%rbp
	retq
	nop

julia> @code_native g(10.)
	.section	__TEXT,__text,regular,pure_instructions
Filename: REPL[2]
	pushq	%rbp
	movq	%rsp, %rbp
Source line: 3
	subq	$64, %rsp
	vmovsd	%xmm0, -8(%rbp)
	movabsq	$4665570672, %rax       ## imm = 0x11616F570
	vmovsd	(%rax), %xmm1           ## xmm1 = mem[0],zero
	movabsq	$fmod, %rax
	callq	*%rax
	vmovsd	-8(%rbp), %xmm1         ## xmm1 = mem[0],zero
	vsubsd	%xmm0, %xmm1, %xmm0
	movabsq	$4665570680, %rax       ## imm = 0x11616F578
	vmulsd	(%rax), %xmm0, %xmm0
	vroundsd	$4, %xmm0, %xmm0, %xmm2
	movabsq	$colon, %rax
	movabsq	$4665570688, %rcx       ## imm = 0x11616F580
	vmovsd	(%rcx), %xmm0           ## xmm0 = mem[0],zero
	leaq	-56(%rbp), %rdi
	vmovapd	%xmm0, %xmm1
	callq	*%rax
	xorl	%eax, %eax
	movq	-24(%rbp), %rcx
	nopl	(%rax)
L112:
	addq	$1, %rax
	cmpq	%rax, %rcx
	jge	L112
Source line: 4
	addq	$64, %rsp
	popq	%rbp
	retq
	nop

Now, when you have more than one input, there where syntax allows you to be more restrictive on the input of your function. For example:

julia> f(x::Real,y::Real) = x + y
f (generic function with 2 methods)

julia> g(x::T,y::T) where {T<:Real} = x + y
g (generic function with 2 methods)

julia> f(1,1.)
2.0

julia> g(1,1.)
ERROR: MethodError: no method matching g(::Int64, ::Float64)
Closest candidates are:
  g(::T<:Real) where T<:Real at REPL[2]:2
  g(::T<:Real, ::T<:Real) where T<:Real at REPL[8]:1

julia> g(1,1)
2

julia> f(1,1)
2

With the where syntax you are able to enforce that both x and y must have the same concrete type.

3 Likes

Thanks for the example. Yes, when there are many arguments I often use the T method to enforce the same type. However, when there is only one argument, I am not sure they are the same. I found out that it doesn’t work on Arrays.

julia> f(x::Array{Real, 2}) = x .+ 1
f (generic function with 3 methods)

julia> g(x::Array{T, 2}) where {T<:Real} = x .+ 1
g (generic function with 3 methods)

julia> a = [1 2; 3 4]
2×2 Array{Int64,2}:
 1  2
 3  4

julia> f(a)
ERROR: MethodError: no method matching f(::Array{Int64,2})
Closest candidates are:
  f(::Array{Real,2}) at REPL[54]:1
  f(::Real) at REPL[41]:1

julia> g(a)
2×2 Array{Int64,2}:
 2  3
 4  5

It doesn’t work because those are different types. An Array{Real, 1} is specifically an array for which each element can any subtype of Real. An Array{Int, 1} is not an Array{Real, 1}. But the syntax Array{T, 1} where T <: Real means an array where each element is of some specific type T which is a subtype of Real. That description matches lots of different arrays, including Array{Int, 1}, Array{Float64, 1}, etc.

2 Likes

You are absolutely right. Thanks for the clarification.

If you don’t need to use the element type inside the function, you can also do:

f(x::Array{<:Real, 2}) = x .+ 1
a = rand(0:9,2,2)
f(a)
1 Like

Yes, of course. I am mainly interested in my first example. In my second example, I was wrong. I simply forgot that Array{Int, 2} is not a subtype of Array{Real, 2} as was pointed out by @rdeits.