top page > computer > haskell > web_lecture > for_programmer > nml2.html
更新日:
文責: 重城良国

NML(Nano Markup Language): 2. 他の型への変換

Books.hs

はじめに

NMLで表現された本のリストをHaskellのデータ構造Bookに変換する。これは方向としてはデコードと考えられる。

型Book

本は題名と作者で表現する。

data Book = Book {
title :: String
author :: String }
deriving Show

変換関数

Nml型の値から[Book]型の値への変換関数をつくる。

NMLによる表現

NMLによる本のリストは以下のような表現とする。

<books>
<book>
<title>The Old Man and the Sea</title>
<author>Ernest Hemingway</author>
</book>

<book>
<title>The Catcher in the Rye</title>
<author>J. D. Salinger</author>
</book>
</books>

このサンプルを文字列として定義する。

books1 :: String
books1 = "<books>" ++
"<book>" ++
"<title>The Old Man and the Sea</title>" ++
"<author>Ernest Hemingway</author>" ++
"</book>" ++
"<book>" ++
"<title>The Cather in the Rye<title>" ++
"<author>J. D. Salinger<author>" ++
"</book>" ++
"</books>"

モジュールData.Maybe, Data.List, Nmlを使うので

import Data.Maybe
import Data.List
import Nml

をモジュールの先頭に追加する。サンプルをNMLとして解析してみる。

% ghci books.hs
*Main> nml books1
Just (Node {rootLabel = "books", subForest = [Node {rootLabel = "book", subForest = [Node {rootLabel = "title", subForest = [Node {rootLabel = "The Old Man and the Sea", subForest = []}]},Node {rootLabel = "author", subForest = [Node {rootLabel = "Ernest Hemingway", subForest = []}]}]},Node {rootLabel = "book", subForest = [Node {rootLabel = "title", subForest = [Node {rootLabel = "The Cather in the Rye", subForest = []}]},Node {rootLabel = "author", subForest = [Node {rootLabel = "J. D. Salinger", subForest = []}]}]}]})

関数booklistNml

関数booklistNmlは1番外側のタグがbooksであることを確認したうえでなかのbookのリストを変換する。

booklistNml :: Nml -> Maybe [Book]
booklistNml (Node "books" bl) = Just $ mapMaybe book bl
booklistNml _ = Nothing

関数bookはまだ作っていない。Nml -> Maybe Book型の関数だ。mapMaybeはMaybe型を返す関数をリストの全要素に適用し、Just値だけを集める。構文エラーや型エラーのチェックのために対話環境で読みこんでみる。関数bookのスタブを用意する。

book :: Nml -> Maybe Book
book = undefined

*Main> :reload
*Main>

構文エラーや型エラーはないようだ。

関数book

関数bookはtitleタグとauthorタグのそれぞれについてそれ自体の存在とその中身の存在を確認し、存在すればそれらから値をとりだしてBook型にまとめる。flip (maybe Nothing) (...)のような形はあとでモナドを学ぶとよりきれいな形に書きなおせる。

book :: Nml -> Maybe Book
book b@(Node "book" _) = flip (maybe Nothing) (get "title" b) $ \t ->
flip (maybe Nothing) (get "author" b) $ \a ->
Just $ Book { title = t, author = a }
book _ = Nothing

複雑なようだがやっていることは単純だ。次に定義する関数getを使って"title"タグ内のテキストをとりだしそれで変数tを束縛し、"author"タグ内のテキストをとりだしそれで変数aを束縛し、それらをBook型にまとめている。(maybe Nothing)を2重にすることでどちらかがNothingならば全体もNothingとなる。

get :: String -> Nml -> Maybe String
get tg (Node _ cs) = case find ((== tg) . rootLabel) cs of
Just (Node _ [Node v []]) -> Just v
_ -> Nothing

関数findは関数filterと似ているが返り値がリストではなくMaybe値となる。もし存在すれば条件を満たすはじめの要素を返す。ここでは子要素のなかからrootLabelがtgであるものをみつけだす。NMLの「文字列」は子要素をもたない節(Node v [])で表現される。つまりNode "tag" [Node "string" []]は<tag>string</tag>を意味する。

*Main> :reload
*Main> let Just n = nml books1
*Main> n
Just (Node {rootLabel = "books", subForest = [Node {rootLabel = "book", subForest = [Node {rootLabel = "title", subForest = [Node {rootLabel = "The Old Man and the Sea", subForest = []}]},Node {rootLabel = "author", subForest = [Node {rootLabel = "Ernest Hemingway", subForest = []}]}]},Node {rootLabel = "book", subForest = [Node {rootLabel = "title", subForest = [Node {rootLabel = "The Cather in the Rye", subForest = []}]},Node {rootLabel = "author", subForest = [Node {rootLabel = "J. D. Salinger", subForest = []}]}]}]})
*Main> booklistNml n
Just [Book {title = "The Old Man and the Sea", author = "Ernest Hemingway"},Book {title = "The Catcher in the Rye", author = "J. D. Salinger"}]

関数booklist

文字列から直接[Book]型の値を入手する関数を作る。

booklist :: String -> Maybe [Book]
booklist s = maybe Nothing booklistNml . nml

*Main> :reload
*Main> booklist books1
Just [Book {title = "The Old Man and the Sea", author = "Ernest Hemingway"},Book {title = "The Catcher in the Rye", author = "J. D. Salinger"}]

モジュールBooks

モジュールにする。先頭を

module Books (Books(..), booklist) where

とする。

まとめ

NMLで表現された本のリストをHaskellのデータ型[Book]に読みこむ関数を作成した。

「NML(1): 構文解析」へもどる 「NML(3): 他の型からの変換」へ

正当なCSSです! HTML5 Powered with CSS3 / styling, and Semantics