Elixir

複数条件をトライするロバストな処理

たとえば、こんな感じの複数条件を次々とトライしていく処理を書くとき、caseで書くと、ネストが深くなって、とにかく醜い

def to_datetime( str ) when is_binary( str ) do
    case str |> case str |> Timex.parse( "%Y/%m/%d", :strftime ) do
        { :ok, result } -> result
        { :error, _   } -> case Timex.parse( "%Y/%m/%d %H:%M:%S", :strftime ) do
            { :ok, result } -> result
            { :error, _   } -> case str |> Timex.parse( "%Y/%m/%d %H:%M:%S.%L", :strftime ) do
                { :ok, result } -> result
                { :error, _   } -> case str |> Timex.parse( "%Y-%m-%dT%H:%M:%SZ", :strftime ) do
                    { :ok, result } -> result
                end
            end
        end
    end
end

条件判定後の処理に類似性があるなら、リスト処理でまとめられて、エレガント(処理的には途中抜けしないため、若干効率は悪いかもだが、条件追加したいときは、リスト追加だけで済む)

def to_datetime( str ) when is_binary( str ) do
    [
        "%Y/%m/%d", 
        "%Y/%m/%d %H:%M", 
        "%Y/%m/%d %H:%M:%S", 
        "%Y/%m/%d %H:%M:%S.%L", 
        "%Y-%m-%dT%H:%M:%SZ", 
    ]
    |> Enum.map( &( str |> Timex.parse( &1, :strftime ) ) )
    |> Enum.filter( &elem( &1, 0 ) == :ok )
    |> List.first
    |> Tpl.ok
end

※上記Tpl.ok()は、タプルで結果を返す関数をパイプ内で止めないための処理

もし、条件判定後の処理に類似性が無いなら、手前で文字列を分解し、後続の関数でパターンマッチ

String.split()の代わりに、Regex.named_captures()を使い、関数側はmapでパターンマッチすることもできる

def to_datetime( str ) when is_binary( str ) do
    str |> String.split( "/" ) |> to_datetime
end
def to_datetime( [ y, m, d, others ] ) do
    # %Y/%m/%d ~のパターンの処理
end
def to_datetime( [ y, m, d ] ) do
    # %Y/%m/%dのパターンの処理
end
def to_datetime( [ tz ] ) do
    # %Y-%m-%dT%H:%M:%SZのパターンの処理
end

手前の関数での文字列パターンマッチも可能ではあるが、表現力が弱く、複雑になるので、早々にあきらめている

def to_datetime( <<number::bytes-size(4)>> <> "/" <> <<number::bytes-size(2)>> <> "/" <> <<number::bytes-size(2)>> ) do
    # %Y/%m/%dのパターンの処理
end
def to_datetime( <<number::bytes-size(4)>> <> "-" <> <<number::bytes-size(2)>> <> "-" <> <<number::bytes-size(2)>> ) do
    # %Y-%m-%dのパターンの処理
end

p.s.上記をアレコレしている中で、if内部でパターンマッチができることが分かった(ただし複数条件を並べるには、elseが入ってきて、あまりキレイとは言えないが、caseの冗長性よりはマシな気がした)

if { :ok, result } = Timex.parse( str, "%Y/%m/%d %H:%M:%S", :strftime ), do: result