Elmメモ - 文字列をIPアドレスに変換(1) splitを用いる方法
IPv4アドレスの文字列"192.168.1.1"をパースする方法を考える。IPAddrの内部表現は次のようにする。
type IPAddr = IPAddr Int Int Int Int
思いつくのは次の2通り。
- ピリオドでsplitして、整数値に変換する。
- パーサを利用する。
いずれにしても結構面倒。この記事では前者だけやる。
準備
適当なディレクトリで次のコマンドを実行。
$ elm init $ elm install elm/parser
src/IPAddr.elm
を作り、内容を以下の通りにする。
module IPAddr exposing (..)
type IPAddr = IPAddr Int Int Int Int
$ elm repl > import IPAddr exposing (..)
方針
次の処理を行う関数をfromString
として定義する。
- 文字列を
.
でsplitする。 - Listの要素数が4でなければ失敗。
- Listの各要素に
String.toInt
を適用し、どれか一つでも失敗すれば全体としても失敗。 - Listを
[a,b,c,d]
としたとき、IPAddr a b c d
を返す。
traverseの実装
3の処理は、次の関数として抽象化できる: リストの各要素にfを適用し、その結果すべてがJust
を返したときだけ、全体の結果を返す。
traverse : (a -> Maybe b) -> List a -> Maybe List b
原始的な実装
なるべくfoldr
とかを使わずに書こうとするとこんな感じになる。
traverse : (a -> Maybe b) -> List a -> Maybe (List b)
traverse f list =
case list of
[] ->
Just []
x::xs ->
case traverse f xs of
Nothing ->
Nothing
Just ys ->
case f x of
Nothing ->
Nothing
Just y ->
Just (y::ys)
case文を使ってネストが深くなってくると、Haskellみたいなパターンマッチが欲しくなってくる。
Maybe.map2を利用した実装
上の実装で、Maybeを2回剥がして値を取り出し、それに対してリストの連結を行なっていることがわかる。実はこの処理を抽象化した関数Maybe.map2
が実装されている。
どちらもJust値だったときのみ、その値を取り出して計算し、Justに包んで返す関数。
> import Maybe > Maybe.map2 (\a b -> a + b) (Just 1) (Just 2) Just 3 : Maybe.Maybe number > Maybe.map2 (\a b -> a + b) Nothing (Just 2) Nothing : Maybe.Maybe number > Maybe.map2 (\a b -> a + b) (Just 1) Nothing Nothing : Maybe.Maybe number > Maybe.map2 (\a b -> a + b) Nothing Nothing Nothing : Maybe.Maybe number
これを利用して書き直すと以下のようになる。
traverse : (a -> Maybe b) -> List a -> Maybe (List b)
traverse f list =
case list of
[] ->
Just []
x::xs ->
Maybe.map2 (::) (f x) (traverse f xs)
これはなかなか感動的。
Maybe.map2 + foldrを利用した実装
上の定義を見ているとfoldrでもっと簡単に書けそうだと気づく。なので書く。
traverse : (a -> Maybe b) -> List a -> Maybe (List b)
traverse f list =
List.foldr (\x acc -> Maybe.map2 (::) (f x) acc) (Just []) list
補足
これは実は車輪の再発明で、同じ関数がelm-community/Maybe.Extraで定義されている。
実はElmにもHoogleみたいに、型名から関数を検索するサービスがある。実際、「こんな型の関数ないかな」と思ってElm Searchで検索したら出てきた。
fromStringの実装
ようやく本題に入る。といってもtraverseさえできればあとは簡単。
まずは、文字列を整数値に変換し、さらに[0,255]に収まっているかどうかをチェックする関数parseByte
を定義する。Maybe.andThen
は以下のように、Maybe
値に対してさらに条件をかけて、不正なものを落とすために使われる。
parseByte : String -> Maybe Int
parseByte string =
String.toInt string
|> Maybe.andThen toByteInt
toByteInt : Int -> Maybe Int
toByteInt n =
if 0 <= n && n <= 255 then
Just n
else
Nothing
これを元にfromString
を実装する。
fromString : String -> Maybe IPAddr
fromString string =
let list = string
|> String.split "."
|> traverse parseByte
in
case list of
Just [a,b,c,d] ->
Just (IPAddr a b c d)
_ ->
Nothing
> IPAddr.fromString "192.168.1.1" Just (IPAddr 192 168 1 1) : Maybe IPAddr.IPAddr > IPAddr.fromString "192.168.1" Nothing : Maybe IPAddr.IPAddr > IPAddr.fromString "192.168.1.1.1" Nothing : Maybe IPAddr.IPAddr > IPAddr.fromString "192.168.1.255" Just (IPAddr 192 168 1 255) : Maybe IPAddr.IPAddr > IPAddr.fromString "192.168.1.256" Nothing : Maybe IPAddr.IPAddr