🍡

Julia で列挙体を使うときのパターン

2024/10/14に公開

Julia で列挙体 (enum) を使うときは以下の様なパターンで書くと良いことが多い。

module Seasons

export Season, spring, summer, autumn, winter
export next, prev, ismild, ishot, iscold

never()::Union{} = throw(ErrorException("something wrong"))

@enum Season::UInt8 begin
    spring
    summer
    autumn
    winter
end

Base.repr(season::Season)::String =
    season == spring ? "⟨spring⟩" :
    season == summer ? "⟨summer⟩" :
    season == autumn ? "⟨autumn⟩" :
    season == winter ? "⟨winter⟩" :
    never()

Base.show(io::IO, season::Season)::Nothing = print(io, repr(season))

Base.show(io::IO, ::MIME"text/plain", season::Season)::Nothing = show(io, season)

Base.string(season::Season)::String =
    season == spring ? "spring" :
    season == summer ? "summer" :
    season == autumn ? "autumn" :
    season == winter ? "winter" :
    never()

Base.print(io::IO, season::Season)::Nothing = print(io, string(season))

Base.tryparse(::Type{Season}, str::AbstractString)::Union{Season, Nothing} =
    str == "spring" ? spring :
    str == "summer" ? summer :
    str == "autumn" ? autumn :
    str == "winter" ? winter :
    nothing

function Base.parse(::Type{Season}, str::AbstractString)::Season
    season = tryparse(Season, str)
    isnothing(season) && throw(ArgumentError("cannot parse $(repr(str)) as Season"))
    season
end

next(season::Season) = Season((UInt8(season) + 0x01) % 0x04)
prev(season::Season) = Season((UInt8(season) + 0x03) % 0x04)
ismild(season::Season) = UInt8(season) % 0x02 == 0x00
ishot(season::Season) = season == summer
iscold(season::Season) = season == winter

end # module Seasons

列挙体を使うときに要素数がそんなバカでかい数になることは少ないハズなので内部表現が UInt8Int8 になるようにする。

デバッグでの表現のために以下の函数を実装する。

  • Base.repr(x::T)::String
  • Base.show(io::IO, x::T)::Nothing
  • Base.show(io::IO, ::MIME"text/plain", x::T)::Nothing

Base.show(io::IO, ::MIME"text/plain", x::T)::Nothing を定義すれば Base.repr(x::T)::String も実装されるが、列挙体の場合は自分で定義した方が速くなる場合が多いと思われる。

文字列との変換のために以下の函数を実装する。

  • Base.string(x::T)::String
  • Base.print(io::IO, x::T)::Nothing
  • Base.tryparse(::Type{T}, str::AbstractString)::Union{T, Nothing}
  • Base.parse(::Type{T}, str::AbstractString)::T

Base.print(io::IO, x::T)::Nothing を定義すれば Base.string(x::T)::String も実装されるが、列挙体の場合は自分で定義した方が速くなる場合が多いと思われる。

tryparse は成功すればパース結果、失敗すれば nothing を返す。parse は成功すればパース結果、失敗すればエラーを投げる。

ML 系の言語のような条件分岐はないため、if ...; elseif ...; else ...; end... ? ... : ...... && ... return; ... && ... return; ... で処理し、最後に Union{} 型の函数を呼び出す。つまりはエラーを投げる。

その他の intrinsic な函数を実装する。

Discussion