Elaboration on Parametric Methods

I don’t fully grasp when you would use parametric types or abstract types in methods.

Before posting my question, I did attempt to read and try and answer the question for myself.

Consider the following code:

abstract type Character end
abstract type Alien <: Character end

struct Gray <: Alien
    name::String
    age::Int32
    favourite_food::String
end

struct BlueAlien <: Alien
    name::String
    favourite_song::String
end

struct Human <: Character
    name::String
end 

Consider the following function that uses the where keyword:

function share_food(character_that_has_food::A,
                    character_that_receives_food::A,
                    food::String) where {A <: Character}
     
     println("Working with $(typeof(character_that_has_food)) and 
             $(typeof(character_that_receives_food))")

     println("Sharing a $(food) with my friend 
             $(character_that_receives_food.name)")
end

mort = Gray("Mort",1000,"Fries")
zorotl = BlueAlien("Zorotl","Blue - Eiffel 65")
share_food(mort,zorotl,"Cheese Burger and Apple Pie")

I don’t see any distinction between the above function and this function:

function share_food(character_that_has_food::Character,
                    character_that_receives_food::Character,
                    food::String)

    println("Working with $(typeof(character_that_has_food)) and   
            $(typeof(character_that_receives_food)).")

    println("Sharing a $(food) with my friend 
             $(character_that_receives_food.name).")
end

Both do the same thing but in a different ways. The first share_food() example uses A as the variable, and I can use that variable just like any other variable.

  1. What situation would you use that variable containing the type? The only situation that I can think of is when you want to do something with a type, but don’t care about the type.

For example, create a collection of aliens generate_alien_vector(aliens_from_db::A) where A <: Alien. Now I don’t care what alien I pass in to generate the vector.

The problem is, with just abstract types I can do the same thing, because Julia knows the specific type it’s given. For example, calling the above share_food() function without the where:

Working with Gray and BlueAlien.
Sharing a Cheese Burger and Apple Pie with my friend Zorotl.

I can even capture the result of typeof() and use it to create a collection.

The example shown in the above link uses generics to allow any type of IBusinessObject but still be able to retrieve the concrete type to call the properties specifc to that type.

  1. Can you carry out a task with parametric functions that you can’t with functions that accept only abstract types?

They are not exactly the same thing:

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

julia> f(1,1.0)
2.0

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

julia> g(1,1.0)
ERROR: MethodError: no method matching g(::Int64, ::Float64)
Closest candidates are:
  g(::T, ::T) where T<:Real at REPL[3]:1
Stacktrace:
 [1] top-level scope
   @ REPL[5]:1

the parametric form only works if both input types are the same.

2 Likes

Edit: @lmiq beat me to it by one minute :wink:

The distinction is that the first method requires that the two characters have the same type (and that that must be some subtype of Character. Here’s a simpler example:

julia> function f(x::T, y::T) where {T <: Integer}
         x + y
       end
f (generic function with 1 method)

julia> f(Int32(1), Int64(1))
ERROR: MethodError: no method matching f(::Int32, ::Int64)
Closest candidates are:
  f(::T, ::T) where T<:Integer at REPL[6]:1
Stacktrace:
 [1] top-level scope
   @ REPL[8]:1

julia> function g(x::Integer, y::Integer)
         x + y
       end
g (generic function with 1 method)


julia> g(Int32(1), Int64(1))
2

A further difference is that having an explicit type parameter lets you express more complicated relationships, such as:

julia> function h(x::T, y::Matrix{T}) where {T}
         x * y
       end
h (generic function with 1 method)

julia> h(2.0, ones(Float64, 2, 2))
2×2 Matrix{Float64}:
 2.0  2.0
 2.0  2.0

here h is a function that will take any x and a y whose scalar type is the same as the type of x, whatever that type may be. That’s not a relationship you can express via dispatch without type variables.

3 Likes