Rubyには連想配列を表すHashクラスが用意されており、文法上もHashを定義するリテラルが存在するなど手厚いサポートがされている。ではHaskellはどうか。Haskellでは連想配列はData.Mapモジュールで提供されている。今日はRubyのHashと、HaskellのData.Mapの使い方を以下のRubyのコードを元に比較してゆきたい。
phoneBook = {"alice" => "1234-5678", "bob" => "2345-6789", "carol" => "3456-7890"}
p phoneBook["alice"] # => "1234-5678"
phoneBook.store("carol", "3456-7890")
p phoneBook["carol"] # => "3456-7890"
phoneBook.delete("bob")
p phoneBook["bob"] # => nil
このRubyのコードは、電話帳を操作するものだ。電話帳はHashのインスタンスで表現し、名前をキーに、電話番号を値とする連想配列とする。まず電話帳を初期化し、それに対して電話帳の項目の追加、削除と名前から電話番号の検索を行う。これをHaskellで実装してみることにする。
import qualified Data.Map as Map
phoneBook :: Map.Map String String
phoneBook = Map.fromList [("alice", "1234-5678"), ("bob", "2345-6789")]
main = do
print $ Map.lookup "alice" phoneBook -- => Just "1234-5678"
let phoneBook2 = Map.insert "carol" "3456-7890" phoneBook
print $ Map.lookup "carol" phoneBook2 -- => Just "3456-7890"
let phoneBook3 = Map.delete "bob" phoneBook2
print $ Map.lookup "bob" phoneBook3 -- => Nothing
HaskellではまずData.Mapモジュールを使用するためにこれを修飾つきでimpomtする。これはPreludeの関数とData.Mapの関数が一部重複するため、明示的にMapを指定しなければData.Mapの関数を呼び出せない形でインポートするためである。RubyのHashとHaskellのData.Mapの基本的なメソッドの対応は以下のとおり。
| Ruby | Data.Map | 意味 |
|---|---|---|
| {}リテラル | Data.Map.fromList | 連想配列を初期化する |
| [] | Data.Map.lookup | キーに対応する値を取得する |
| store | Data.Map.insert | キーに対応する値を設定する |
| delete | Data.Map.delete | キーとその値を削除する |
HaskellではfromLinstを使ってタプル(ペア)のリストから連想配列を初期化できる。 Haskellの関数はいずれも連想配列自体は変更せずに、新しい連想配列を返すようになっている。 そのためinsertやdelete毎に新しい連想配列を変数に束縛している。 これはちょっと不恰好に見えるのだが、直接連想配列を操作する方法は見当たらなかった。
注目すべきはlookup関数の型である。これをGHCiで調べてみると以下のようにMaybeがついた特別な型が返ることがわかる。これはその型の値か、Nothingが返ることを意味している。実際、電話帳からbobの項目を削除したあとでbobを検索するとNothingが返ってくることがわかる。
*Main> :t Map.lookup
Map.lookup :: Ord k => k -> Map.Map k a -> Maybe a
なおHaskellのプログラムで頻出している$、$に続く関数の評価を優先して行うためのシンタックスシュガーである。printの例ではprint $ 1 + 1とprint (1 + 1)が等価である。$以降がすべて括弧で囲われているものと思えば良い。
電話帳を題材にしたData.Mapの使い方については、”すごいHaskellたのしく学ぼう!”の6章を参考にした。本書ではまず自力でタプルとリストを使った連想リストを実装したあとに、Data.Mapを導入する流れとなっており、良い勉強になる。
オーム社
売り上げランキング: 126,678

