Generics in Julia

Julia supports generics natively, allowing for the creation of flexible and reusable code. Let’s explore how to use generics in Julia through some examples.

using Printf

# As an example of a generic function, `slices_index` takes
# a collection of any type and an element of that type,
# and returns the index of the first occurrence of v in s,
# or nothing if not present.
function slices_index(s, v)
    for (i, elem) in enumerate(s)
        if elem == v
            return i
        end
    end
    return nothing
end

# As an example of a generic type, `List` is a
# singly-linked list with values of any type.
mutable struct List{T}
    head::Union{Nothing, ListNode{T}}
    tail::Union{Nothing, ListNode{T}}
end

mutable struct ListNode{T}
    next::Union{Nothing, ListNode{T}}
    val::T
end

# We can define methods on generic types just like we
# do on regular types.
function push!(lst::List{T}, v::T) where T
    new_node = ListNode{T}(nothing, v)
    if isnothing(lst.tail)
        lst.head = lst.tail = new_node
    else
        lst.tail.next = new_node
        lst.tail = new_node
    end
end

# all_elements returns all the List elements as an array.
function all_elements(lst::List{T}) where T
    elems = T[]
    current = lst.head
    while !isnothing(current)
        push!(elems, current.val)
        current = current.next
    end
    return elems
end

function main()
    s = ["foo", "bar", "zoo"]

    # When invoking generic functions, we can often rely
    # on type inference. Note that we don't have to
    # specify the types when calling `slices_index` - 
    # the compiler infers them automatically.
    @printf("index of zoo: %s\n", slices_index(s, "zoo"))

    lst = List{Int}()
    push!(lst, 10)
    push!(lst, 13)
    push!(lst, 23)
    @printf("list: %s\n", all_elements(lst))
end

main()

To run the program, save it as generics.jl and use the julia command:

$ julia generics.jl
index of zoo: 3
list: [10, 13, 23]

In this Julia example, we’ve implemented similar functionality to the original code:

  1. We defined a generic function slices_index that works with any collection type.
  2. We created a generic List type along with its associated ListNode type.
  3. We implemented methods for the List type, including push! and all_elements.
  4. In the main function, we demonstrated the use of these generic constructs.

Julia’s syntax for generics is somewhat different from other languages:

  • Type parameters are specified using curly braces {} in type definitions.
  • In function definitions, type parameters are declared using the where clause.
  • Julia uses Union{Nothing, T} to represent optional values, similar to T | null in some other languages.

Julia’s type system is quite powerful and flexible, allowing for expressive generic programming while maintaining performance through type specialization at compile-time.