Search code examples
classinterfacejuliatyping

Julia type system and behavior


I'm still trying to wrap my head around Julia's types. For example assuming I wanted to define the structure of an Animal object. Taking as example typescript I would do something like this:

interface Animal {
    sound: string;
}

function sing(animal: Animal) {
    console.log(animal.sound);
}

const dog: Animal = {
    sound: 'woof',
};

// OR
class Dog implements Animal {
    sound = 'woof';
    // other Dog methods here
    run() {}
}

// Wrong implementations are caught immediately
const cow: Animal = {
    noise: 'moo'
}

// Error: Type '{ noise: string; }' is not assignable to type 'Animal'.
// Object literal may only specify known properties, and 'noise' does not exist in type 'Animal'.

How can I do this in Julia? In other words, Animal needs to have a .sound. This is what I tried:

abstract type Animal end

function sing(animal::Animal) 
    println(animal.sound)
end

struct Dog <: Animal
    sound::AbstractString
end

function run(dog::Dog) end

struct Cow <: Animal
    noise::AbstractString
end

But:

  • I don't get any warning from IDE that sing will not work with all the Animal implementation
  • When I try to run sing(Cow("moo")) I do get an error, but at the function sing!: ERROR: type Cow has no field sound

What am I missing? How can I maintain a large codebase if I get no warning before runtime? How can I define behaviour since I can't subtype concrete classes?


Solution

  • Julia is not object oriented so taking OO is usually not comfortable.

    Most often you will want to use multiple dispatch to define behavior for different object types eg.:

    abstract type Animal end
    
    getsound(::T) where T <: Animal = throw(ErrorException("not implemented for $T"))
    struct Dog <: Animal end
    struct Cow <: Animal end
    struct Cat <: Animal
       specialsound::String
    end
    getsound(::Dog) = "hau"
    getsound(cat::Cat) = cat.specialsound
    sing(animal::Animal) = println(getsound(animal))
    

    And here is the usage:

    julia> sing(Dog())
    hau
    
    julia> sing(Cat("mew"))
    mew
    
    julia> sing(Cow())
    ERROR: not implemented for Cow
    

    Now regarding IDE - commenting out getsound(::T) in VS Codium does not underline the cow. However this indeed looks like something that could be added as compiler detects this as an error (assuming getsound(::T) where T <: Animal is not defined) :

    julia> @code_typed sing(Cow())
    CodeInfo(
    1 ─     Main.getsound(animal)::Union{}
    └──     unreachable
    ) => Union{}