「ほっ」と。キャンペーン

<   2013年 06月 ( 14 )   > この月の画像一覧

中国江西省で鉄鋼会社が倒産

ロイターの6月28日のウェブニュースによると、「中国東部の江西省で鉄鋼会社が債務を返済できず、破産宣告を受けて閉鎖された。」とのことだ。

倒産したのは、Jiangxi Pingte Iron and Steel で融資枠の突然の打ち切りが原因らしい。供給過剰による需要鈍化と価格低下にも関わらず生産を続けざるを得ない状況で、返済資金が枯渇したらしい。自転車操業を続けても需要の改善の見込みがたたず力つきたのだろう。

しかし、気になるのは、「突然の融資枠の打ち切り」という文言だ。その背後に金融危機による市場に流通する資金の枯渇があるとしたら、第2第3の倒産が目の前だという事にならないだろうか。

中国の経済崩壊は本当に来るのだろうか。来るとすればどのような形になるのだろうか。また、日本や世界への影響はどの程度深刻なものになるのだろうか。経済の専門家ではないので、不安だけが募る。
[PR]
by tnomura9 | 2013-06-29 23:55 | 話のネタ | Comments(0)

中国のATMからお金が出なくなった。

5月31日から上海総合が大幅に下落しているが、【新唐人2013年06月26日付ニュース】によると「6月23日、中国各地の工商銀行ATMが相次ぎ故障し、現金の引き出しが不能になりました。」とのことだ。

銀行の流動資金が枯渇しATMから現金を払い出す事ができなくなったのだ。表向きはシステムのバージョンアップということになっているが、ATM から現金が取り出せない故障は各方面の銀行に広がっているらしい。

なぜ銀行から現金がなくなったのかというと、それは、陰の銀行を通じて地方政府の不動産投資に無制限に貸し出されたからだ。これらの不動産投資は入居者のないビルを大量に建築し、当然資金の回収はできず大量に投資された資金が紙くずに変わってしまった。

日本のバブル崩壊と同じようなものだが、その無軌道さと規模が違う。この7月にも、中国発の経済的な大地震が発生しようとしている。日本もその経済的津波の影響を免れる事はできないだろう。

ここしばらくは、経済ニュースから目を離せない。
[PR]
by tnomura9 | 2013-06-27 23:27 | 話のネタ | Comments(0)

ちょっとゆるい動きなのだけれどちゃんとタイミングが合っている。ゆるふわ系。

ソロ

【雪】GIFT*踊ってみました【魔法をかけるから】HD

コラボ

【Θ&雪】いーあるふぁんくらぶ / 1, 2, Fanclub を踊ってみた.HD

1年25組

【1年25組】ぼくとわたしとニコニコ動画 踊ってみた!!【5人Ver.】HD

プレイリスト

Nico Nico D@nce
[PR]
by tnomura9 | 2013-06-27 08:05 | ボーカロイド | Comments(0)

中国は保有する米国債で米国を脅せるか

中国は保有する米国債で米国を脅せるか/アシナガバチ

というサイトを見つけた。中国が米国債を売却してアメリカの経済を破綻させようとしても、「米国債は登録制で、しかも米国の安全保障などに敵対する国家の保有分は国際緊急事態経済権限法等により無効化出来る」そうだ。

要するに米国に敵対すると、アメリカは借金をすぐにでも踏み倒せるという事だ。敵国への借金は安全保障上の問題になるから、当然の措置という見方もできるのだろうが、金を貸した方が逆に身動きが取れなくなってしまうという仕組みのようにも見える。

米国と中国を見比べると、どうしても中国がアマチュアのように見えてしまう。
[PR]
by tnomura9 | 2013-06-26 21:49 | 話のネタ | Comments(0)

All About Monads 読解 16

The Writer monad

Overview

Computations (モナド値) のタイプ:
 処理の結果とともにデータストリームを発生させる処理 computation

バインド演算子の動作:
 Writer モナドの値は (計算値、ログの値)のペアだ。バインド演算子は左項の計算値をとって、左のK関数を関数適用させ、その計算のログを右項のログにアペンドする。

どんな処理に有用か:
 ログや計算の過程で副次的なデータを発生させる処理に有用。

zero と plus :
 なし

代表的なデータ型:
 Writer [String] a

Motivation

プログラムの処理の過程でログなどの情報を計算結果とは別に発生させるのが便利な事が多い。ログやトレースはその主な例だ。処理の過程で得られた結果と同時に、その過程を反映させる情報の記録も欲しい。

ログの作成を直接的にプログラムするのはコードが汚くなり、ログのエントリーを間違えたりなどのつまらないバグも紛れ込みやすい。Writer モナドを使えば、このようなログの出力をメインのプログラムから隠蔽してすっきりしたコードを書くことができる。

Definition

次に示す定義は、multi-parameter タイプクラスと funDeps という言語拡張を使っている。標準の Haskell 98 レポートにはない仕様だ。しかし、Writer モナドを使いこなすのに、これらの言語拡張の詳細を知る必要はない。

Writer モナドの定義を完全に理解するためには、Haskell の Monoid クラスについて知っておく必要がある。Monoid クラスはモノイドと呼ばれる数学的構造を Haskell で表現したものだ。これは、Monad クラスがモナドという数学的構造を Haskell で表現しているのと同様だ。

うれしいことに、モノイドの構造はモナドの構造より簡単だ。モノイドの構造は集合とその集合の要素である単位元と、その集合上で定義された2項演算からなっている。ただし、この2項演算は結合法則を満たしている必要がある。モノイドはいくつかの数学的法則に従っている必要がある。2項演算の値は必ずその集合の元でなければならないし。単位元と他の要素の間の2項演算の値はその他の要素自身でなければならない。これらの性質は MonadPlas クラスの mzero と mplus の性質に合致している。従って MonadPlus のインスタンスでもあるモナドは同時にモノイドでもある。

数学的構造としてのモノイドの例としては、自然数と単位元としての0とそれらの2項演算である加算からなる構造がある。また、自然数と単位元1と2項演算の積からなる数学的構造もモノイドである。

Haskell ではモノイドは代数的データ型と単位元と2項演算からなっている。Haskell には Data.Monoid モジュールに Monoid クラスが定義されている。このクラスにはモノイドを扱うための関数が備えられている。単位元は mempty で定義され、2項演算は mappend として定義されている。

Haskell で最も頻繁に使われるモノイドはリストである。しかし、a -> a 型の関数もモノイド構造を作っている。

リストを Writer モナドのモノイドとして使うときは注意が必要だ。リストは結合の処理効率が悪いので mappend によってデータが巨大になる場合は、処理速度に問題が発生する。このような場合はリストに換えてもっとアペンドの効率の良いデータ型を採用する必要がある。

newtype Writer w a = Writer { runWriter :: (a,w) }

instance (Monoid w) => Monad (Writer w) where
    return a             = Writer (a,mempty)
    (Writer (a,w)) >>= f = let (a',w') = runWriter $ f a in Writer (a',w `mappend` w')

Writer モナドは (value, log) ペアをコンテナに保持している。また、log のデータ型はモノイドである必要がある。return 関数は value を空の log とペアにして返す。バインド演算子は左項の value に右項の関数を関数適用させた値を戻すが、その過程で発生した log を左項の log に追加し、新しい value とペアにして戻り値にする。

class (Monoid w, Monad m) => MonadWriter w m | m -> w where
  pass   :: m (a,w -> w) -> m a
  listen :: m a -> m (a,w)
  tell   :: w -> m ()

instance (Monoid w) => MonadWriter (Writer w) where
  pass   (Writer ((a,f),w)) = Writer (a,f w)
  listen (Writer (a,w))   = Writer ((a,w),w)
  tell   s         = Writer ((),s)

listens :: (MonadWriter w m) => (w -> w) -> m a -> m (a,w)
listens f m = do (a,w) <- m; return (a,f w)

censor :: (MonadWriter w m) => (w -> w) -> m a -> m a
censor f m = pass $ do a <- m; return (a,f)

MonadWriter クラスには Writer モナドを扱うための多くのユーティリティ関数が定義されている。最も単純で有用な関数は tell だ。1個または複数個のエントリーを log に追加する。listen 関数は Writer モナド値をとり、コンテナの値 (a, w) と w のペア ((a, w), w) を Writer モナドにラッピングして返す。この操作でモナドの中にログの値を取り込む (listen) ことができる。

pass 関数の動作は少し複雑だ。pass 関数は Writer モナドの値を引数にするが、モナド値のコンテナないの値は ((a,f), w) である必要がある。つまり (val, log) の val の部分が値 a と関数 f のペアである。pass 関数の出力値については値 a と関数 f を w に関数適用した f w を新しいログの値として、ペア (a, f w) を返す。この動作は少々わかりにくいので、普通はヘルパー関数の censor が使われる。censor 関数は、関数と Writer (モナド値) を引数に取り、pass 関数で使えるように加工した Writer (モナド値) を pass 関数に渡す。

listens 関数は、listen と同じ動作だが、関数 f も引数に取り、ログに f を関数適用した Writer (a, f w) を返す。

管理人注:

上の説明は All about monad を訳しただけのもので、実際に Writer モナドをどう使うかはよくわからなかった。すっかり忘れていたが、自分のブログ記事に Writer モナドの使い方を簡単にまとめていた。下の例はそのうちの ghci の実行例を抜き出したものだ。また、その記事へのコメントでhttp://learnyouahaskell.com/for-a-few-monads-more#writerを紹介していただいた。

Prelude> import Control.Monad.Writer
Prelude Control.Monad.Writer> runWriter (tell "hello")
Loading package transformers-0.3.0.0 ... linking ... done.
Loading package mtl-2.1.1 ... linking ... done.
((),"hello")
Prelude Control.Monad.Writer> runWriter (do tell "hello"; return 3)
(3,"hello")
Prelude Control.Monad.Writer> runWriter (listen $ tell "hello")
(((),"hello"),"hello")
Prelude Control.Monad.Writer> runWriter (do tell "one "; x <- return 1; tell "two "; y <- return 2; tell "add"; return (x+y))
(3,"one two add")
Prelude Control.Monad.Writer> runWriter (do xs <- return [1,2,3]; tell ("list: " ++ (show xs) ++ " -> "); tell "length"; return (length xs))
(3,"list: [1,2,3] -> length")

Example

example17.hs は非常に単純なファイアーウォールのプログラムだ。これは、ルールにしたがってパケットをフィルターする。ルールは、パケットの source と destination のホスト名および、パケットの payload だ。ルールにマッチしたパケットだけがフィルターを通過する。ファイアーウォールの主な目的はパケットのフィルタリングだが、同時に動作状況ののログも取りたい。

example17.hs はコピペで動作するが、import するライブラリ名の編集が必要だ。また、動作テストのために rules.txtpackets.txt が必要だ。

import System.IO
import Control.Monad
import System.Environment
import Data.Maybe
import Data.List
import Control.Monad.Writer

実行例は次のようになる。

Prelude> :l example17.hs
[1 of 1] Compiling Main ( example17.hs, interpreted )
Ok, modules loaded: Main.
*Main> :main rules.txt packets.txt
Loading package transformers-0.3.0.0 ... linking ... done.
Loading package mtl-2.1.1 ... linking ... done.
ACCEPTED PACKETS
Packet {from = Host 23, to = Host 7, payload = Data "web request"}
... (以下省略)

下のプログラムは、ログを取る処理のシンプルな記述だ。

-- this is the format of our log entries
data Entry = Log {count::Int, msg::String} deriving Eq

-- add a message to the log
logMsg :: String -> Writer [Entry] ()
logMsg s = tell [Log 1 s]

-- this handles one packet
filterOne :: [Rule] -> Packet -> Writer [Entry] (Maybe Packet)
filterOne rules packet = do rule <- return (match rules packet)
                            case rule of
                              Nothing  -> do logMsg ("DROPPING UNMATCHED PACKET: " ++ (show packet))
                                             return Nothing
                              (Just r) -> do when (logIt r) (logMsg ("MATCH: " ++ (show r) ++ " <=> " ++ (show packet)))
                                             case r of
                                               (Rule Accept _ _) -> return (Just packet)
                                               (Rule Reject _ _) -> return Nothing

上のプログラムは非常に単純だ。しかし、同じ内容の一連のログはマージしてしまいたいときはどうすればいいのだろうか。上に述べた関数ではそのような機能を持ったものはない。しかし、"delayed logging" のテクニックを使えば、現在のエントリーをそのままログに残すのではなく、新しいエントリーがルールにマッチしない時のみログに残すようにすることができる。下のプログラムが "delayed logging" を使って、同じ内容のメッセージのエントリーをマージしている。

-- merge identical entries at the end of the log
-- This function uses [Entry] as both the log type and the result type.
-- When two identical messages are merged, the result is just the message
-- with an incremented count.  When two different messages are merged,
-- the first message is logged and the second is returned as the result.
mergeEntries :: [Entry] -> [Entry] -> Writer [Entry] [Entry]
mergeEntries []   x    = return x
mergeEntries x    []   = return x
mergeEntries [e1] [e2] = let (Log n msg)   = e1
                             (Log n' msg') = e2
                         in if msg == msg' then
                              return [(Log (n+n') msg)]
                            else
                              do tell [e1]
                                 return [e2]

-- This is a complex-looking function but it is actually pretty simple.
-- It maps a function over a list of values to get a list of Writers,
-- then runs each writer and combines the results.  The result of the function
-- is a writer whose value is a list of all the values from the writers and whose
-- log output is the result of folding the merge operator into the individual
-- log entries (using 'initial' as the initial log value).
groupSame :: (Monoid a) => a -> (a -> a -> Writer a a) -> [b] -> (b -> Writer a c) -> Writer a [c]
groupSame initial merge []     _  = do tell initial
                                       return []
groupSame initial merge (x:xs) fn = do (result,output) <- return (runWriter (fn x))
                                       new             <- merge initial output
                                       rest            <- groupSame new merge xs fn
                                       return (result:rest)
    
-- this filters a list of packets, producing a filtered packet list and a log of
-- the activity in which consecutive messages are merged
filterAll :: [Rule] -> [Packet] -> Writer [Entry] [Packet]
filterAll rules packets = do tell [Log 1 "STARTING PACKET FILTER"]
                             out <- groupSame [] mergeEntries packets (filterOne rules)
                             tell [Log 1 "STOPPING PACKET FILTER"]
                             return (catMaybes out)

前へ 目次 次へ
[PR]
by tnomura9 | 2013-06-23 01:28 | Haskell | Comments(0)

最近のピンキー

ソロ

【ピンキー!】ハロ/ハワユ 踊ってみた【(「`・ω・)」】

【ぼっちで】Bad ∞ End ∞ Night 踊ってみた【ピンキー!】

【ピンキー!】Girls 踊ってみた

チャンネル

pinky ayane

まりやんとピンキー

【まりやん&ピンキー!】 トゥインクル を踊ってみた 【やんキー!】HD
[PR]
by tnomura9 | 2013-06-21 08:16 | ボーカロイド | Comments(0)

All About Monads 読解 15

The Reader monad

Overview

Computations (モナド値) のタイプ
 共通の環境の値を利用する computation に使われる。

バインド演算子の動作:
 モナド値は環境 (environment) を引数とし、値を戻値とする関数だ。バインド演算子は左項の戻値に右後の関数を関数適用させるが、左項と右項は共通の環境を利用する。

どんな処理に有用か:
 モナドの関数どうしで、変数の値や、環境を共有している場合。

Zero と plus
 なし

代表的な型:
 Reader [(String,Value)] a

Motivation

ある種のプログラムでは、共有する環境で動作する場合がある。この場合の環境とは、たとえば、複数の変数とその値である。こういうプログラムでは、環境変数の値を読んだり、環境変数の値を変更したりする。しかし、これらのプログラムは State モナドを使うほど汎用化されている必要がない。

Reader モナドはこれらのプログラムに特化してデザインされていて、State モナドを使うよりは簡潔で読みやすい記述ができる。

Definition

次に示す定義は、multi-parameter タイプクラスと funDeps という言語拡張を使っている。標準の Haskell 98 レポートにはない仕様だ。しかし、Reader モナドを使いこなすのに、これらの言語拡張の詳細を知る必要はない。

newtype Reader e a = Reader { runReader :: (e -> a) }

instance Monad (Reader e) where
    return a         = Reader $ \e -> a
    (Reader r) >>= f = Reader $ \e -> runReader (f (r e)) e

Reader モナドのコンテナの中のデータは e (環境) -> a 型の関数だ。Reader モナドの (モナド型関数を >>= で連結した連鎖の) 最終的な値を得るのは簡単だ。単に (runReader reader) environment を実行するだけだ。

return 関数は単に値 a を返す関数 \e -> a を作って Reader 型のコンテナに入れるだけだ。バインド演算子 (>>=) の動作は少し複雑だ。まず左項の (Reader r) パターンによって関数 r :: e -> a が取り出される。また右項の関数 f :: a -> Reader e a は、a 型の値を引数に取り、Reader e a 型を返す関数だ。この関数 f は Reader モナドの Kleisli 射である。バインド演算子は、無名関数 \e -> runReader (f (r e)) e を作成して、Reader データコンストラクタのコンテナに入れる。

Reader データコンストラクタで Reader e a 型のコンテナに入れられる無名関数

/e -> runReader (f (r e)) e

についてみてみると、これは1引数 e の関数だ。この引数 e にまず r が関数適用される。その戻値は a 型の値だ。これに f を関数適用すると Reader e a 型の値が戻る。さらにこの Reader e a 型の値に runReader アクセサを関数適用して得られる e -> a 型の関数を e に関数適用すると a 型の値が戻される。こうしてできた e -> a 型の無名関数を Reader データコンストラクタで Reader e a 型にくるんでバインド演算子の動作が完了する。

ややこしい仕組みだが、これのおかげで、環境(変数)をモナドのプログラムから隠蔽しながら次のように、モナド値と Kleisli 射との連携を作っていくことができる。

Prelude> import Control.Monad.Reader
Prelude Control.Monad.Reader> runReader (return 2) [("var0",0)]
2
Prelude Control.Monad.Reader> runReader (return 2 >>= \a -> return (a*a)) [("var0",0)]
4

このように、バインド演算子は左項の Reader モナド値を抽出して左の Kleisli 関数に引数として与えるが、両者とも共通の環境を共有している。

class MonadReader e m | m -> e where
    ask   :: m e
    local :: (e -> e) -> m a -> m a

instance MonadReader (Reader e) where
    ask       = Reader id
    local f c = Reader $ \e -> runReader c (f e)

asks :: (MonadReader e m) => (e -> a) -> m a
asks sel = ask >>= return . sel

MonadReader クラスは数多くの便利な関数を Reader モナドに提供している。たとえば、ask 関数は環境の値を取り出す。これは普通 selector または lookup 関数と組み合わせて使われる。

Prelude Control.Monad.Reader> runReader (ask) [("var0",0)]
[("var0",0)]
Prelude Control.Monad.Reader> runReader (do env <- ask; let (Just val) = lookup "var0" env in return val) [("var0",0)]
0

また、 local 関数は第1引数で環境を加工して第2引数の Kleisli 射を実行する。

Prelude Control.Monad.Reader> runReader (local (("var1",1):) ask) [("var0",0)]
[("var1",1),("var0",0)]

このように、どのモナドも return 関数でデータをモナド値にラッピングし、>>= 演算子で左項のモナド値から生のデータを取り出して、右項の Kliesli 射を関数適用するという点は共通している。言い換えると、この return と >>= の働きを掴んでいたら、モナドのプログラムはどんなモナドについても皆同じ様式でプログラムできる事になる。

Example

example16.hs はテンプレートファイルを読み込んで、環境を設定するプログラムだ。テンプレートファイルでは変数の使用やテンプレートのインクルードができる。Readerモナドを利用することで、ask 関数と local 関数を使うことができる。ask 関数は環境からデータを読み出し、local 関数は環境の編集を行う。

example16.hs はコピペで動かすことができるが、import 部分の変更が必要だ。

import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Token

import System.IO.Error hiding (try) -- "try" is also defined in the Parsec libraries
import Control.Monad
import System.Environment
import Data.Maybe
import Data.List (intersperse)
import Control.Monad.Reader

また、example16.hs をテストするために template.txt ファイルが必要になるのでこれもコピーしておく必要がある。

実行例は次のようになる。

Prelude> :l example16.hs
[1 of 1] Compiling Main ( example16.hs, interpreted )
Ok, modules loaded: Main.
*Main> :main template.txt '$<#1>'
Loading package transformers-0.3.0.0 ... linking ... done.
Loading package bytestring-0.9.2.1 ... linking ... done.
Loading package mtl-2.1.1 ... linking ... done.
Loading package array-0.4.0.0 ... linking ... done.
Loading package deepseq-1.3.0.0 ... linking ... done.
Loading package text-0.11.2.0 ... linking ... done.
Loading package parsec-3.1.2 ... linking ... done.
'
this is just plain text, it contains $, | and [ but no actual
template!

= boring but necessary test
'*Main>

example16.hs は main 関数の部分を除くと3つのパートからなっている。1つ目はテンプレートのデータ型を定義した部分だ。それぞれのデータ型は Show クラスのインスタンスにしてあるので、ghci で試して見ることができる。

*Main> T "hello"
hello
*Main> V (T "hello")
${hello}
*Main> Q (T "hello")
$"hello"
*Main> D (T "hello") (T "world")
hello=world
*Main> I (T "hello") [(D (T "foo") (T "bar"))]
$<hello|foo=bar>
*Main> C [(T "hello"), (V (T "world"))]
hello${world}
*Main> NT "hello" (T "world")
[hello]world[END]

2番めの部分は、テンプレートファイルから文字列を取り出すためのパーサの記述だ。

*Main> parse name "" "[hello]"
Right "hello"
*Main> parse end "" "[END]"
Right "[END]"
*Main> parse templateFile "" "[#1]world[END]"
Right [[#1]world[END]
]

3番目の部分は環境からデータを読みだしたり、書き込んだりする部分だ。この部分に Reader モナドが使われている。環境は Environment 型で定義されている。

data Environment = Env {templates::[(String,Template)], variables::[(String,String)]}

*Main> templates (Env [("hello", T "world")] [("foo", "bar")])
[("hello",world)]

lookupVar 関数は環境の変数名から、変数の内容を取り出す。

*Main> lookupVar "foo" (Env [("hello", T "world")] [("foo", "bar")])
Just "bar"

lookupTemplate 関数は環境のテンプレート名からテンプレートを取り出す。

*Main> lookupTemplate "hello" (Env [("hello", T "world")] [("foo", "bar")])
Just world

addDefs は環境の variables 部分に新しい定義を追加する。

*Main> variables (addDefs [("hoge", "fuga")] (Env [("hello", T "world")] [("foo", "bar")]))
[("hoge","fuga"),("foo","bar")]

Reader モナドが使われているのは resolve 関数だ。

resolveDef 関数は Definition 型を (String, String) 型に変換する Reader モナドの関数。したがって、Reader 型からコンテナ内の関数を runReader 関数で取り出して使う。

*Main> runReader (resolveDef (D (T "foo") (T "bar"))) (Env [("", T "")] [("","")])
("foo","bar")

Reader モナドに特徴的な関数は環境の値を取り出す ask 関数だ。example16.hs の例では ask 関数ではなく ask 関数のユーティリティ関数 asks だ。asks 関数の定義は次のようになる。

asks :: (MonadReader r m) => (r -> a) -> m a
asks f = do
    r <- ask
    return (f r)

つまり、環境を ask で取り出してそれに関数 f を関数適用した結果を return 関数で返す。

*Main> runReader (asks (*2)) 2
4

asks を使った resolve 関数の定義は次のようになる。

resolve (V t)    = do varName  <- resolve t
                      varValue <- asks (lookupVar varName)
                      return $ maybe "" id varValue

resolve の引数が (V t) 型の場合 t の値を取り出して、それをキーに環境を検索し、キーがヒットしたらその値を返すという動作をする。

*Main> runReader (resolve (V (T "foo"))) (Env [("hoge", T "fuga")] [("foo", "bar")])
"bar"

上のプログラムの maybe 関数は Data.Maybe モジュールの関数で、型は次のようになる。

*Main> :t maybe
maybe :: b -> (a -> b) -> Maybe a -> b

第1引数は初期値で、第2引数は関数 f で、第3引数が Maybe a 型だ。第3引数が Just x のときは x に関数 f を適用して f x を返し、Nothing の時は初期値をそのまま帰す。ghci でテストすると次のようになる。

*Main> runReader (asks (*2)) 2
4

また、local を使った定義は resolve 関数の引数が (I t ds) の場合に見られる。

resolve (I t ds) = do tmplName <- resolve t
                      body     <- asks (lookupTemplate tmplName)
                      case body of
                        Just t' -> do defs <- mapM resolveDef ds
                                      local (addDefs defs) (resolve t')
                        Nothing -> return ""

上の resolve (I t ds) は、最初にテンプレート名 t のテンプレートを環境から検索する。仮に検索されたテンプレートが V (T String) 型だったとする。また、local の第1引数は、環境を加工する。この場合 addDefs だから環境 Env {templates :: [(String, Template)], variables :: [(String,String)]} の variables リストに、(ローカルに)引数の定義リストを付け加える。local の第2引数は (resolve (V (T String))) だから variables リストの中から String に合致するペアを探索することになる。この場合(ローカルに)環境に付け加えられたリストも検索することになる。

*Main> runReader (resolve (I (T "foo") [D (T "bar") (T "baz")])) (Env [("foo", V (T "qux"))] [("qux", "foobar")])
"foobar"

*Main> runReader (resolve (I (T "foo") [D (T "bar") (T "baz")])) (Env [("foo", V (T "bar"))] [("qux", "foobar")])
"baz"

Haskell のプログラムは関数で記述されているので、個々の関数のテストが ghci で簡単にできる。ソースの読解は、個々の関数のテストをしているうちに自然に分かってくる。

次に示すのが All About Monads に掲載されている example16.hs の Reader モナドに関する部分の抜粋だ。asks と local の出現している当たりに注目すると良いだろう。

-- This the abstract syntax representation of a template
--              Text       Variable     Quote        Include                   Compound
data Template = T String | V Template | Q Template | I Template [Definition] | C [Template]
data Definition = D Template Template

-- Our environment consists of an association list of named templates and
-- an association list of named variable values.
data Environment = Env {templates::[(String,Template)],
                        variables::[(String,String)]}

-- lookup a variable from the environment
lookupVar :: String -> Environment -> Maybe String
lookupVar name env = lookup name (variables env)

-- lookup a template from the environment
lookupTemplate :: String -> Environment -> Maybe Template
lookupTemplate name env = lookup name (templates env)

-- add a list of resolved definitions to the environment
addDefs :: [(String,String)] -> Environment -> Environment
addDefs defs env = env {variables = defs ++ (variables env)}
                      
-- resolve a Definition and produce a (name,value) pair
resolveDef :: Definition -> Reader Environment (String,String)
resolveDef (D t d) = do name <- resolve t
                        value <- resolve d
                        return (name,value)

-- resolve a template into a string
resolve :: Template -> Reader Environment (String)
resolve (T s)    = return s
resolve (V t)    = do varName  <- resolve t
                      varValue <- asks (lookupVar varName)
                  return $ maybe "" id varValue
resolve (Q t)    = do tmplName <- resolve t
                      body     <- asks (lookupTemplate tmplName)
                      return $ maybe "" show body
resolve (I t ds) = do tmplName <- resolve t
                      body     <- asks (lookupTemplate tmplName)
                      case body of
                        Just t' -> do defs <- mapM resolveDef ds
                                      local (addDefs defs) (resolve t')
                        Nothing -> return ""
resolve (C ts)   = (liftM concat) (mapM resolve ts)

前へ 目次 次へ
[PR]
by tnomura9 | 2013-06-17 13:36 | Haskell | Comments(0)

ぷぷっぴどぅ〜

ソロ

【ぷぷっぴ】チルドレンレコード / Children Record【メイド♡】

コラボ

【アルスマグナ】えれくとりっく・えんじぇぅ / Electric Angel【タツキ】

リスト

Nico Nico D@nce
[PR]
by tnomura9 | 2013-06-17 08:02 | ボーカロイド | Comments(0)

All About Monads 読解 14

The State monad

Overview

Conputation (モナド値) のタイプ:
 状態を保持できる計算 (モナド値としてのデータ)

バインド演算子の動作:
 違う値を同じ変数に保持できない関数型プログラミングに、見かけ上の状態を作り出す。一連のデータ処理をバインド演算子で繋ぐが、暗黙に状態を引き渡すように見せかける事ができる。

何に有用か:
 状態を共有する処理をつなげて処理を行う場合に使われる。

Zero と plus:
 なし

代表的なデータ型:
 State st a

Motivation

関数型言語では参照透明性を破壊しないように、変数の値の書き換えはできない。状態の必要な処理の場合は次のように、状態値を次々に受け渡していくような記述が必要だが、煩雑だし、扱いづらい。

data MyType = MT Int Bool Char Int deriving Show

makeRandomValue :: StdGen -> (MyType, StdGen)
makeRandomValue g = let (n,g1) = randomR (1,100) g
                        (b,g2) = random g1
                        (c,g3) = randomR ('a','z') g2
                        (m,g4) = randomR (-n,n) g3
                    in (MT n b c m, g4)

State モナドを使うとこのような共通の状態を利用する関数を接続して使う際に、情報を見かけ上隠してしまう事ができるので、読みやすく、メンテナンスのしやすいコードを書くことができる。

管理人注:

State モナドがどういう仕組みで働いているのかということについては、『 All about monad 』 には詳しくは述べられていない。管理人のブログ記事の『State モナド』では、State型のデータ型のパラメータが2つある理由や、State s 型のデータのコンテナが s -> (s,a) 型の関数で、おまけにフィールドに runState というフィールド名がついているために runState アクセサでコンテナ内の s -> (s, a) 型のデータが取り出せることなどの、State モナドの不可解な動作を ghci で検証している。

これを見れば State モナドを使うときは do ブロックの中で return と >>= を使ってプログラムを記述し、runState でモナド値のコンテナの中の s -> (a, s) 型の関数を取り出し、その関数の引数に状態の初期値を与えることでプログラムの動作が完了することがわかる。まとめると、State モナドの使い方は、

runState (do {return a; ... ; ... ; \x -> return f(x)}) s

または、

runState (return a >>= ... >>= \x -> return (fx)) s

のような使い方になる。

Definition

State モナドには multi parameter タイプクラスや funDeps という言語拡張が使われているが、State モナドを利用する上では細かい知識は必要ない。

newtype State s a = State { runState :: (s -> (a,s)) }

instance Monad (State s) where
    return a = State $ \s -> (a,s)
    (State x) >>= f = State $ \s -> let (v,s') = x s in runState (f v) s'

State モナドのコンテナの値は状態 s から状態 s と値 a のペア (a,s) への遷移関数 s -> (a,s) だ。State s a というタイプコンストラクタの解釈は「状態 s のもとでの値 a」という意味になる。

State s a 型のタイプコンストラクタは2つのタイプ変数をもつので、State s 型が Monad タイプクラスのインスタンスになる。このあたりも Maybe 型などと異なるところなので注意が必要だ。return 関数は単に状態遷移関数の値を設定するだけだ。この際状態の値には影響しない。bind 関数は左項のモナド値のコンテナから状態遷移関数 x を取り出し、それを現在の状態 s に適用して新しい値 v と状態 s' を作り、右項のKliesli 射を関数適用させてできた新しい遷移関数を State データコンストラクタでラッピングする。

class MonadState m s | m -> s where
    get :: m s
    put :: s -> m ()

instance MonadState (State s) s where
    get   = State $ \s -> (s,s)
    put s = State $ \_ -> ((),s)

MonadState クラスは標準的で非常にシンプルなインターフェース関数を State モナドに与える。get 関数は状態の値を State モナドの値に取り込み、put 関数は状態の値を設定するが State モナドの値には影響を与えない。

get と put 以外にも多くの関数が State モナドに提供されている。そのうちで gets は関数を引数に取り、それを状態に適用させて State モナドの値として返す。その他の関数については HaskellWiki の『State Monad』の記事に詳しい。

Example

次の単純なプログラム例は、乱数発生時にシードを状態として受渡して隠すことで、複数回の乱数の発生を簡潔に記述している。

example15.hs はコピペで実行できるが、import の部分を次のように変更する必要がある。

import Control.Monad
import System.Environment
import System.IO
import System.Random
import Control.Monad.State

実行例は次のようになる。

Prelude> :l example15.hs
[1 of 1] Compiling Main ( example15.hs, interpreted )
Ok, modules loaded: Main.
*Main> :main
MT 99 False 'b' (-57)
MT 99 False 'b' (-57)

example14.hs のコードの中心部分は次のようになる。

整数、論理値、文字、整数をコンテナに治める MyType 型を作る。

data MyType = MT Int Bool Char Int deriving Show

{- Using the State monad, we can define a function that returns
   a random value and updates the random generator state at
   the same time.
-}

getAny 関数は1個の乱数を返す。put で乱数のシード StgGen の値を更新している。

getAny :: (Random a) => State StdGen a
getAny = do g      <- get
            (x,g') <- return $ random g
            put g'
            return x

getOne は範囲 (lowest, highest) の乱数を1個返す。この場合も put 関数で StgGen の状態値を変更している。

-- similar to getAny, but it bounds the random value returned
getOne :: (Random a) => (a,a) -> State StdGen a
getOne bounds = do g      <- get
                   (x,g') <- return $ randomR bounds g
                   put g'
                   return x

makeRandomValueST 関数は getAny と getOne を利用して複数の乱数を発生させている。乱数発生関数間の状態 StdGen の受け渡しを隠蔽できるので記述が読みやすい。

{- Using the State monad with StdGen as the state, we can build
   random complex types without manually threading the
   random generator states through the code.
-}  
makeRandomValueST :: StdGen -> (MyType, StdGen)
makeRandomValueST = runState (do n <- getOne (1,100)
                                 b <- getAny
                                 c <- getOne ('a','z')
                                 m <- getOne (-n,n)
                                 return (MT n b c m))

前へ 目次 次へ
[PR]
by tnomura9 | 2013-06-16 18:45 | Haskell | Comments(0)

All About Monads 読解 13

The IO monad

Overview

Computation (モナドの値)のタイプ:
 IO処理を行う computation (モナドの値)。IO モナドではアクションと呼ばれる。

バインド演算子の動作:
 IO アクションは結合された順番に実行される。Failure は IO エラーを発生させる。エラーは捕捉してエラー処理を記述する事ができる。

どんな場合に有用か:
 Hasekll のプログラムの中で IO 処理を行う。

Zero と plus :
 定義されていない。

代表的な型:
 IO a 型

Motivation

IO処理は純粋関数と一緒には使えない。IO 処理は参照透明ではなく副作用を持っているからだ。IO モナドはこの問題を解決する。それは、IO 処理を行う computation (モナドの値) を IO モナドの中に閉じ込めてしまうからだ。

管理人注:

モナドの意味や威力を理解するのには Maybe モナドからはいるのが分かりやすい。しかし、モナドとは何かというのを理解するのには IO モナドが一番典型的な例を提供してくれる。

モナドとは何かという問いに対する答えをネットを探すと混乱するくらい様々なものが見られる。しかし、モナドとは単にタイプコンストラクタ m と return 関数と >>= 演算子の三つ組みのことだ。言い換えると、この3つの記号の使い方が分かっていれば、モナドは完全に使いこなせるという事だ。

タイプコンストラクタ m は関手である。関手という用語に惑わされなければ、これは、Haskell の値を Haskell の別の値に対応させる関数だ。IO a は a という値を単に IO a 型の値に対応づけているだけだ。a も IO a も Haskell 圏の値なので、IO というデータコンストラクタは関手のなかでも自己関手になる。

また、return x = IO x なので、return 関数は Haskell 圏の値 x を IO モナドの圏の値 IO x に写す関数だ。IO モナドでは IO x というデータコンストラクタ IO を利用する形で IO モナド値を作る事はできないが、retrun 関数がこれを実行してくれる。

最後に >>= は右項に IO a 型のデータをとり、左項に a -> IO b 型の Kleilsi 射 (関数) をとる。>>= 演算の値は左項の IO a からコンテナ内の a を取り出してこれに右項の Kleisli 射を関数適用した値だ。したがって、これも IO b 型となるから IO モナド値になる。

従って、return x で Haskell の値 x を IO モナドの世界に放り込み、その後次々に >>= 演算子を介して Kleisli 射を作用させていってもその結果の値は全て IO モナド値なので、return と >>= だけを使って計算をしていればその値は全て IO モナド値になってしまう。

このように return x で投げ込まれた値は二度と IO モナドの世界から出て来れないので、たとえ Kleisli 射の関数が副作用を持っていたとしても、その影響は IO モナドの世界に閉じ込められてしまって、純粋関数の世界の値に影響する事はない。

この一度入ったら出て来れないという性質は、IO モナドに限らず全てのモナドに共通だ。他のモナドはモナド内の値を取り出す事ができるように見えるが、それはパターンマッチやフィールド名によるアクセサなど、モナドの性質以外の機能を用いて取り出しているだけだ。

モナドとは、return と >>= 演算子を使って、Haskell の値をモナド m の世界に閉じ込めてしまう方法だというイメージをもっていれば、IO モナドに限らず全てのモナドを使いこなす事ができる。

Definition

IO モナドの定義は処理系によるので、IO モナドの値を作るためのデータコンストラクタ IO は使えないし、IO モナドの値を取り除く関数もない。このことで IO モナドは一方向モナドになる。このため、副作用のある関数の値は全て IO モナドの中に閉じ込められてしまい、それらの値や手続き型言語風のIO処理のプログラムは純粋関数から隔離される。

このチュートリアルを通してモナドの値は computation と読んできたが、IO モナドの値はしばしば IO アクション と呼ばれている。ここの説明ではその用語を使う事にする。

Haskell のトップレベルの関数 main の型は IO () でなければならない。従って Haskell のプログラムはトップレベルでは手続き型風にアクションを並べたプログラムになる。純粋関数はこれらのアクションから呼び出される形で利用される。IO モジュールがエクスポートしている関数は直接 IO 処理を行う事はなく、IO アクションを値として返すだけだ。IO アクションは IO 処理が行われるための情報を持っている。IO アクションは IO モナドの中で組み合わされて複合アクションが作られる。それらは最終的に組み合わされて IO () を返す main 関数の値になる。

標準の Prelude と IO モナドモジュールには多くの IO モナドの関数が定義されている。多くの Haskell プログラマーは明らかにそれらに馴染みがあるはずだ。このチュートリアルでは IO モナドのモナドとしての性質に焦点を当てる。そのため、多様な IO モナド関数全てについては述べない。

IO タイプコンストラクタは、Monad クラスと MonadError クラスのインスタンスだ。また、エラー識別子は Error.fail 関数で投げられる。このエラー識別子は String 型を引数として作られる。IO モナドでは例外処理の仕組みは Control.Monad.Error モジュールが利用できる。このモジュールを利用するためには Control.Monad.Error を import しなくてはならない。同じような仕組みは IO モジュールの ioError と catch 関数でも構成できる。

ghci で確認してみた。Prelude の中でも ioError と catch が定義されている。

Prelude> :i ioError
ioError :: IOError -> IO a -- Defined in `GHC.IO.Exception'
Prelude> :t catch
catch :: IO a -> (IOError -> IO a) -> IO a

Control.Monad.Erro を import すると、noMsg, strMsg, throwError, catchError が使える。

Prelude> import Control.Monad.Error
Prelude Control.Monad.Error> :t noMsg
noMsg :: Error a => a
Prelude Control.Monad.Error> :t strMsg
strMsg :: Error a => String -> a
Prelude Control.Monad.Error> :t throwError
throwError :: MonadError e m => e -> m a
Prelude Control.Monad.Error> :t catchError
catchError :: MonadError e m => m a -> (e -> m a) -> m a

IO モナドの定義は次のようになる。コンパイラなどの処理系に依存している。

instance Monad IO where
    return a = ...   -- function from a -> IO a
    m >>= k  = ...   -- executes the I/O action m and binds the value to k's input  
    fail s   = ioError (userError s)

data IOError = ...

ioError :: IOError -> IO a
ioError = ...
  
userError :: String -> IOError
userError = ...

catch :: IO a -> (IOError -> IO a) -> IO a
catch = ...

try :: IO a -> IO (Either IOError a)
try f = catch (do r <- f
                  return (Right r))
              (return . Left)

IO モナドは Monad Template Library フレームワークに組み込まれており、MonadError クラスのインスタンスである。

instance Error IOError where
  ...

instance MonadError IO where
    throwError = ioError
    catchError = catch

IO モジュールは便利な try 関数を提供している。try 関数は IO アクションを実行し成功すれば Right result を返し、IO アクションでエラーが発生すれば Left IOError を返す。

Prelude> import System.IO.Error
Prelude System.IO.Error> try (putStrLn "hello")
hello
Right ()
Prelude System.IO.Error> try (readFile "foo")
Left foo: openFile: does not exist (No such file or directory)

Example

example14.hs は tr コマンドの部分的な実装だ。tr コマンドはコマンドライン引数の指定に従って、標準入力の文字列の置き換えを行う。このプロラム例は IO モナドでエラー処理を MonadError クラスを使って行うプログラムを提示している。

example14.hs はコピペで実行できるが、import するモジュールを次のように変更する必要がある。

import Control.Monad
import System.Environment
import System.IO
import Control.Monad.Error

実行例を次に示す。今回は ghc でコンパイルする必要がある。

********-no-MacBook-Pro:~ ********$ ghc -o ex14 --make example14.hs
Linking ex14 ...
********-no-MacBook-Pro:~ ********$ echo hello world | ./ex14 "aeiou" "x"
hxllx wxrld
********-no-MacBook-Pro:~ ********$ ./ex14
Usage: ex14 set1 set2
Translates characters in set1 on stdin to the corresponding
characters from set2 and writes the translation to stdout.

example14.hs の中心部分は次のようになる。

import Monad
import System
import IO
import Control.Monad.Error

set1 の文字列の中の文字にマッチする文字を set2 の文字に置き換える。

-- translate char in set1 to corresponding char in set2
translate :: String -> String -> Char -> Char
translate []     _      c = c
translate (x:xs) []     c = if x == c then ' ' else translate xs []  c
translate (x:xs) [y]    c = if x == c then  y  else translate xs [y] c
translate (x:xs) (y:ys) c = if x == c then  y  else translate xs ys  c

文字セット set1 と set2 のルールで文字列 str の文字を置き換える。

-- translate an entire string
translateString :: String -> String -> String -> String
translateString set1 set2 str = map (translate set1 set2) str

エラーが発生したときに Usage (使用法) を表示する。

usage :: IOError -> IO ()
usage e = do putStrLn "Usage: ex14 set1 set2"
             putStrLn "Translates characters in set1 on stdin to the corresponding"
             putStrLn "characters from set2 and writes the translation to stdout."

標準入力の文字列に文字の置き換えを行って、標準出力へ出力する。

-- translates stdin to stdout based on commandline arguments
main :: IO ()
main = (do [set1,set2] <- getArgs
           contents    <- hGetContents stdin
           putStr $ translateString set1 set2 contents)
       `catchError` usage

前へ 目次 次へ
[PR]
by tnomura9 | 2013-06-12 05:49 | Haskell | Comments(0)