Hatena::Grouphaskell

Haskell卒業!

  Haskellの勉強 -> 演習 -> 卒業
  Haskell&プログラミング卒業しました。その他サイコなことは「route150の日記」に書いています。

2010年05月07日 金曜日

[][]応用文法(モナド変換子1) 05:59

対応しているモナドによって動作が変わるので、なかなか面倒...


基本的に内部モナドと合成モナド(外殻みたいな感じ)に分けて考えるらしい。一応、2つのモナドの性質を持つのだけど、これが組み合わせ、順番によって変わるので、モナド変換子のbindの定義をよく調べないと予期しない動作をする。基本的には通常、姿の見えない内部モナドの性質の方が強い...


大抵の場合、合成モナドのbindの際に内部モナドのbindも行われる。これが厄介で内部モナドがListやMaybeだと複雑な演算になる。Treeだと何が起こるかわからないくらいになる。内部モナドは逆にIOとかの方が扱いやすいかも...


lift演算は、基本的に内部モナドをbindした値をそのまま取り出せるように実装されているので、逆に安心して(?)使える。定義も簡単。合成モナドのbindを理解する方が難しい...気がする...


見た感じ、継続モナドとIOモナドとか意外と相性が良さそう。モナドの殻を外す時は、基本的に外殻のモナドから外してゆく。つまり、内部モナドの方が演算の基本部分に強く関わっている(?)。つか、モナド変換子のliftとかbindの定義を見ても、内部モナドの演算が核で、それに付加する感じで機能を付け足している...


語弊があるかもしれないけど、基本的な演算は内部モナドに依存すると考えていいかもしれない(姿が見えないだけに混乱する)。数学分かる人なら説明できるかも...


とりあえず、色々試してみる...


import System.Random (newStdGen)
import Control.Applicative ((<$>))
import Test.QuickCheck
import Test.QuickCheck.Gen (Gen (..))
import Control.Monad.List
import Control.Monad.State
import Control.Monad.Cont


-- Genモナドに生成器とサイズパラメータを与え、乱数値を生成するIOアクションを生成する...
genToIO n g = flip (unGen g) n <$> newStdGen

-- 合成((Gen)) - 内部モナドほど、括弧の内側...
-- 自然数乱数値を生成...
test1 = (>>= print) . (genToIO 10) $ do
  arbitrary `suchThat` (> 0) :: Gen Int

-- $> test1
-- 14

-- 合成(ListT(Gen))
-- 5個の自然数乱数値のリストを生成...
test2 = (>>= print) . (genToIO 10) . runListT $ do
  i <- ListT $ return [1 .. 5]
  x <- lift $ (arbitrary `suchThat` (> 0) :: Gen Int)
  return x

-- $> test2
-- [16,16,3,31,6]

-- 合成(StateT(ListT(Gen)))
-- 5個の自然数乱数値のリストを生成。但し、その自然数は10回の自然数を生成した時の最大値...
-- Stateが5個に分かれてる(StateTより内部モナドのListTの影響(?)が強い?)...
test3 = (>>= print) . (genToIO 10) . runListT . (`execStateT` 0) $ do
  i <- lift $ ListT $ return [1 .. 5]
  forM_ [1 .. 10] $ \i -> do
    x <- lift . lift $ (arbitrary `suchThat` (> 0) :: Gen Int)
    modify $ max x

-- $> test3
-- [30,52,46,31,60]

-- 合成(ContT(StateT(ListT(Gen))))
-- 5個の自然数乱数値のリストを生成。但し、その自然数は10回の自然数を生成した時の最大値。
-- 但し、10回の試行内、1度でも10以上の値が出たら、生成を止めてそれまでの最大値を採用...
test4 = (>>= print) . (genToIO 10) . runListT . (`execStateT` 0) . (`runContT` return) $ do
  callCC $ \exit1 -> do
    i <- lift . lift $ ListT $ return [1 .. 5]
    forM_ [1 .. 10] $ \i -> do
      x <- lift . lift . lift $ (arbitrary `suchThat` (> 0) :: Gen Int)
      when (x >= 10) $ exit1 ()
      lift . modify $ max x

-- $> test4
-- [0,0,7,0,0]

内部モナドが演算の基本っぽいこと(持ち上げという意味)が確認できた気がする...


通常は、lift . liftなどは持ち上げの順番がわかるような名前の関数にすると良いらしい...


test4の[0, 0, 7, 0, 0]とかあり得ないのが、多分、生成器の精度があんまり良くないため(だと思う)。標準ライブラリの乱数生成の精度は良くないらしい。最近、乱数生成系の関数をよく使うけど、初回評価時とか同じ数が連続する気がする。まぁ、いいけど...(追記: よく考えたら、そうでもなかった。Haskell >>= say "ゴメンナサイ")


test4とかモナドのキメラ、又はフランケンシュタイン。なんか最強の合成モナドを作るとか流行らないかな。内部モナドがPokemonモナドとかなら対戦できるな。誰もやらないだろうけど...


複雑なので理解が間違ってる可能性のほうが高いです...orz

[][]応用文法(モナド変換子2) 07:28

前にやったListTのモナド変換子(内部モナドがMaybeの場合)を復習してみる...


この場合の、Maybeのリフト演算はC/C++的な局所領域脱出(?)に相当する。MaybeがNothingを返すと、計算結果はNothingになってしまうので。内部モナドのMaybeが演算の柱となっていることがわかる。guardは、合成モナドで使うとcontinue相当になるかな...


あんまり、いい記述の仕方じゃないけど。C/C++に変換して考えた方が最初はいいかもと思ったので...

import Control.Monad.List
import Control.Monad

_MAYBE = lift
_FOREACH = ListT . return
_CONTINUE = guard . not

maybeList :: [Maybe Int] -> Maybe [Int]
maybeList xs = runListT $ do
  m <- _FOREACH xs ; do
    x <- _MAYBE m
    _CONTINUE $ x `rem` 7 == 0
    return x

-- $> maybeList [Just 5, Just 7]
-- Just [5]

Cっぽく書くとこんな感じ...


/* C-ish pseudo code */
#define N 100

typedef int bool_t;

struct{
  bool_t b;
  int x; 
} maybes[N];
int result[N], i, count = 0;

/* 適当にmaybesを初期化... */
...

for(i = 0; i < N; i++){
  if(!maybes[i].b){
    /* 多重ループじゃなければ、count = 0; break; でいいけど、
       空の時もあるので一応... */
    goto FAILED;
  }else if(x % 7 == 0){
    continue;
  }else{
    result[count++] = maybes[i].x;
  }
}

/* SUCCEEDED */
if(count > 0)
  ...

return;

debugbreak;
FAILED:
...

よく考えたら、内部モナドが演算の根底にあって、それを合成して(対応するパラレルワールドに持ち込んで)いるのだから、内部モナドが演算を支配しているのは当たり前だった...


多分、数学的に適切な概念があるのだろうけどわからない...orz

[][]Parsec1(Road to Parsec) 11:17

やっと、Parsec...


見る限り、今のところ最強のモナドっぽい。これで出来ないことなんてあるのだろうかと...


モナドなので、例によってbindのカラクリを調べてみる。とりあえず、ParsecTはモナド変換子で、

data ParsecT s u m a
    = ParsecT { runParsecT :: State s u -> m (Consumed (m (Reply s u a))) }

と定義されてる。sは状態、uはユーザー状態(一応、正格らしい)、mは内部モナド、aは多分、任意の型。基本仕様でユーザー状態を扱えるという豪華さ...


Consumed、Replyの構造はシンプルなので先にやってしまうと、

data Consumed a  = Consumed a                     -- 入力を消費した場合(多分...)
                 | Empty !a                       -- しなかった場合

data Reply s u a = Ok a !(State s u) ParseError   -- 成功
                 | Error ParseError               -- 失敗

で、入力を消費したかしないかで、ComsumedかEmptyに分岐、そのそれぞれについてOkかErrorに分岐という2段階分岐で、結果は結局、OkかErrorの2種類...


で、bind(>>=)の実装はと言うとシンプルで、まず、入力を消費したかをbindすることで確認、ComsumedかEmptyのそれぞれについてOkかErrorかをbindすることで確認するといった感じ...


fmapはOkの値を修正するだけで、Errorの場合は何もしない...


MonadPlusのインスタンスとしては、動作はMaybeに似ていて、mzeroが失敗、mplusが

最初に見つかった成功値のようだ...


因みに、ParsecTは以下の型クラスインスタンス...

MonadReader r m => MonadReader r (ParsecT s u m)
MonadState s m => MonadState s (ParsecT s' u m)
MonadError e m => MonadError e (ParsecT s u m)
MonadTrans (ParsecT s u)
Monad m => Monad (ParsecT s u m)
Monad m => Functor (ParsecT s u m)
Monad m => MonadPlus (ParsecT s u m)
Monad m => Applicative (ParsecT s u m)
Monad m => Alternative (ParsecT s u m)
MonadCont m => MonadCont (ParsecT s u m)
MonadIO m => MonadIO (ParsecT s u m)

MonadPlus(モノイド)のインスタンスなら、Alternativeインスタンスにもできるはずだけど、Alternativeのメソッド(many、some)はListやMaybeでは使えなかった(戻ってこない)。でもParsecTでは使える。


通常使うモナド版のParsecはIdentityモナドを使って、

type Parsec s u = ParsecT s u Identity

と定義されている。この状態でも十分に強力なのだけど、Identityモナドの代わりにIOモナドと合成すれば格段に強力になる。入力を受け取り、その内容をパースして出力を行うという演算を簡単に実現できると思うし、速度が必要なら純粋関数を使えばいいし...


bindが素直な定義なのでIO、Cont系とは相性が良さそう...

-- 最初なので、Alternativeのmany、someだけ使ってみる...
import Control.Applicative ((<$>), Applicative (..), Alternative (..))
import Text.ParserCombinators.Parsec hiding (many, (<|>))
import Text.ParserCombinators.Parsec.Char (char)

t :: Parser ()
t = do
  char 'a'
  return ()

testMany = parseTest (many t) "bcd"
testSome = parseTest (some t) "bcd"

-- 期待通りの結果になった...
-- $> testMany
-- []
-- $> testSome
-- parse error at (line 1, column 1):
-- unexpected "b"
-- expecting "a"

[][]Parsec2 14:10

継続モナドとIOモナドを合成してみた...


持ち上げ演算さえしなければ、気が付かないのではないかと思うくらい相性がいい気がする...

import Control.Applicative ((<$>), Applicative (..), Alternative (..))
import Control.Monad.Cont (ContT (..), lift, liftIO)
import Text.Parsec
import Text.ParserCombinators.Parsec hiding (many, (<|>))
import Text.ParserCombinators.Parsec.Combinator
import Text.ParserCombinators.Parsec.Char
-- import qualified Data.Set as Set
-- import qualified Data.Map as Map

type MyParser a u = ParsecT String u (ContT (Either ParseError a) IO) a

liftIOToParsec ::  IO a -> MyParser a u
liftIOToParsec   = liftIO

liftContToParsec :: ContT (Either ParseError a) IO a -> MyParser a u
liftContToParsec = lift

runMyParser :: (Show a, Show u) => MyParser a u -> u -> String -> IO (Maybe a) 
runMyParser p u s = do
  res <- (`runContT` return) $ runPT p u "" s
  case res of
    Left  err -> return $ Nothing
    Right x   -> return $ Just x

main = solution >>= putStrLn . maybe "failed." show where
  solution = getContents >>= runMyParser begin
    ()
--  (Set.empty :: Set.Set Integer)
--  (Map.empty :: Map.Map Integer Integer)

-- runMyParserまでしっかり型を書いたので、後は推論してくれるはず。
-- コンビネータの使い方が全くわからないけど、とりあえず入力を確認してみる...
begin = do
  c <- anyChar
  liftIOToParsec $ print c
  c <- begin
  return ()

無限ループなので、適当に止めてください...


地道に使い方を覚えないと...

[][]Parsec3 18:38

Parsec2のコードが酷すぎる(型とか間違っていたので)ので書き直してみた...


コンビネータを少し覚えた。何度も演習に使っているCode Jam 2009 Alien Languageを解いてみた(と言うよりも、入出力ができているかの確認)。


最新のParsecライブラリはText.ParserCombinators.Parsecではなく、Text.Parsecのものらしい...

import Control.Applicative ((<$>), Applicative (..), Alternative (..))
import Control.Monad.Cont (ContT (..), lift, liftIO)
import Control.Monad
import Text.Parsec hiding (many, (<|>))
import Text.Parsec.Combinator
import Text.Parsec.Char
import Text.Printf

type MyParser r u a = ParsecT String u (ContT (Either ParseError r) IO) a

-- 連結ってないのかな...
(<&>) :: MyParser r u a -> MyParser r u b -> MyParser r u b
a <&> b = a >> b                  ; infixl 5 <&>

-- F#(OCaml?)っぽく...
ignore :: Monad m => a -> m ()
ignore = const (return ())

liftIOToParsec   :: IO a -> MyParser r u a
liftIOToParsec   = liftIO

liftIOToCont     :: IO a -> ContT (Either ParseError r) IO a
liftIOToCont     = liftIO

liftContToParsec :: ContT (Either ParseError r) IO a -> MyParser r u a
liftContToParsec = lift

runMyParser :: (Show a, Show u) => MyParser a u a -> u -> String -> IO (Either String a) 
runMyParser p u s = do
  res <- (`runContT` return) $ runPT p u "" s
  case res of
    Left err -> return $ Left (show err)
    Right x  -> return $ Right x

main :: IO ()
main = solution >>= either putStrLn ignore where
  solution = readFile "./A-large-practice.in" >>= runMyParser solve ()

integer :: MyParser r u Int
integer = read <$> spaces <&> some digit <?> "integer"

line :: MyParser r u String
line = spaces <&> some lower <?> "line"

word :: MyParser r u String
word = between (char '(') (char ')') (some lower)
  <|> return <$> lower
  <?> "word"

-- 一応、ParsecTの合成モナドなんだけど、IOモナドとして全く違和感が無い...
solve :: MyParser r u ()
solve = do
  [l, d, n] <- count 3 integer
  ass <- count d line
  bss <- count n (spaces <&> count l word)
  liftIOToParsec $ zipWithM_ (display ass) [1 .. n] bss
  where
    display ass i bs = printf
      "Case #%d: %d\n" i (sum [fromEnum . and $ zipWith elem as bs | as <- ass])

Parsec最強!


コード長くなるけど...


あと、珍しく型を全部書いてるけど、モノイドだからコピペでほとんど終わる...

[][]CodeJam 2010 22:32

なんかカウントダウンが始まってる...

一応、参加登録はしてあるから、多分、参戦するけどコンテストとか苦手...

templateだけ、作っておこう...


問題を読み取るパーサーと、解を表示する高階関数に渡す関数を記述するだけの状態にしといた。Parsecのいい演習になるかも...


予選はまだ時間的な余裕があるけど、本戦とか上位者でもかなり下準備しないと難しそう...

[][][]CodeJam 2010 Templete 22:36

24時間あるっていっても、コピペで済むに越したこと無いから...


やば、貼り付けてみるとimportライブラリだけで結構あるなぁ...


でも、説明読んだら、1ファイル100KB(複数でMax:1MB)らしい。それ考えるともの足りない。10万行くらい書いてもzipすれば100KB多分いかないからなぁ...

------------------------------ Templates begin ------------------------------
-- ##### Language extensions #####
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ViewPatterns, PatternGuards, BangPatterns, TupleSections #-}
-- {-# OPTIONS_GHC -fglasgow-exts #-}
{-# OPTIONS_GHC -cpp #-}
{-# OPTIONS_GHC -O2 -fvia-C -optc-O3 #-}

-- ##### Import libraries #####
-- import System.Random (getStdRandom, newStdGen, random, randomR, randoms, randomRs)
import Control.Applicative ((<$>), Applicative (..), Alternative (..))
import Control.Monad.Cont (ContT (..), lift, liftIO)
import Control.Monad.State
import Control.Monad.ST
import Control.Monad.List
import Control.Monad
import Control.Arrow ((&&&), (***), first, second, app)
-- import Control.DeepSeq
-- import Data.Bits
import Data.Ord (comparing)
import Data.Function (on)
-- import Data.Ratio
import Data.Char
-- import Data.Maybe
import Data.Array
-- import Data.Array.Unboxed
-- import Data.Array.IO
import Data.Array.ST
-- import Data.IORef
import Data.STRef
import Data.List
import Data.Tree
-- import Data.Monoid (Monoid (..), mconcat)
-- import Data.MemoTrie (memo, memo2)
import Numeric (showIntAtBase)
-- import qualified Data.Set as Set
-- import qualified Data.Map as Map
-- import qualified Data.Foldable as Foldable
-- import Data.Sequence (ViewR (..), ViewL (..), viewr, viewl)
import Foreign.Marshal.Utils (fromBool, toBool)
import Text.Parsec hiding (many, (<|>))
import Text.Parsec.Combinator
import Text.Parsec.Char
import Text.Printf (printf, PrintfArg)
import System.Win32.Types (maybeNum, numToMaybe)
-- import Test.QuickCheck
-- import Test.QuickCheck.Arbitrary
-- import Test.QuickCheck.Gen (Gen (..))
-- import Test.QuickCheck.Property
-- import Test.QuickCheck.Instances.Char
-- import Test.QuickCheck.Instances.List
-- import Test.QuickCheck.Instances.Num
import Debug.Trace (trace, traceShow)

-- ##### Debug & Trace #####
#define _DEBUG

infixr 0 .$.
infixr 9 ...

#ifdef _DEBUG
(.$.) f x   = traceShow x $ f x
(...) f g x = let x' = g x in traceShow x' $ f x'
_TRACE  x   = traceShow x x 
_TRACEM x   = traceShow x (ignore x)
_TRACEP x   = liftIOToParsec $ print x
#else
(.$.)       = ($)
(...)       = (.)
#define _TRACE(x)  x
#define _TRACEM(x)
#define _TRACEP(x)
#endif

(?) b x y  = if b then x else y                   ; infixr 1 ?
(??) x y b = (?) b x y                            ; infixr 1 ??

-- ##### Utils #####
ignore :: Monad m => a -> m ()
ignore = const (return ())

space'       = (>> putChar ' ')
endl'        = (>> putChar '\n')

readInt      = read :: String -> Int
readInteger  = read :: String -> Integer
readInts     = map readInt
readIntegers = map readInteger

-- ##### List #####
splitBy n = takeWhile (not . null) . map (take n) . iterate (drop n)

elim, sortR :: Ord a => [a] -> [a]
elim = map head . group . sort
sortR      = sortBy (flip compare)
sortByFst, sortBySnd, sortByFstR, sortBySndR :: (Ord a, Ord b) => [(a, b)] -> [(a, b)]
sortByFst  = sortBy (comparing fst)
sortBySnd  = sortBy (comparing snd)
sortByFstR = sortBy (flip (comparing fst))
sortBySndR = sortBy (flip (comparing snd))
groupByFst, groupBySnd :: (Eq a, Eq b) => [(a, b)] -> [[(a, b)]]
groupByFst = groupBy ((==) `on` fst)
groupBySnd = groupBy ((==) `on` snd)

-- ##### Array #####
array1D     x  = listArray (0, x - 1)
array2D (y, x) = listArray ((0, 0), (y - 1, x - 1))

arrayToLists n a = splitBy n $ elems a
arrayToList  n a = concat $ arrayToLists n a

arrayST1D' a = runST $ do
  let b@(lb, ub) = bounds a
  let r = range b
  ma <- thaw a     :: ST s (STArray s Int Int)
--  mr <- newSTRef 0 :: ST s (STRef s Int)
  forM_ r $ \x -> do
  -- Do something here
    return ()
  --
  getElems ma

arrayST2D' a = runST $ do
  let b@(lb, ub) = bounds a
  let r = range b
  ma <- thaw a     :: ST s (STArray s (Int, Int) Int)
--  mr <- newSTRef 0 :: ST s (STRef s Int)
  forM_ r $ \p@(y, x) -> do
  -- Do something here
    return ()
  --
  getElems ma

-- ##### Tree #####
searchTree fM f initData = evalState (fM f initData)

-- dfs
dfsTree = searchTree unfoldTreeM

-- bfs
bfsTree = searchTree unfoldTreeM_BF

-- ##### Parser #####
type MyParser r u a = ParsecT String u (ContT (Either ParseError r) IO) a

(<&>) :: MyParser r u a -> MyParser r u b -> MyParser r u b
a <&> b = a >> b                                   ; infixl 5 <&>

liftIOToParsec   :: IO a -> MyParser r u a
liftIOToParsec   = liftIO

liftIOToCont     :: IO a -> ContT (Either ParseError r) IO a
liftIOToCont     = liftIO

liftContToParsec :: ContT (Either ParseError r) IO a -> MyParser r u a
liftContToParsec = lift

runMyParser :: MyParser a u a -> u -> String -> IO (Either String a) 
runMyParser p u s = do
  res <- (`runContT` return) $ runPT p u "" s
  case res of
    Left err -> return $ Left (show err)
    Right x  -> return $ Right x

run :: IO String -> MyParser a u a -> u -> IO ()
run ios p userData = solution >>= either putStrLn ignore where
  solution = ios >>= runMyParser p userData

integer :: MyParser r u Int
integer = read <$> spaces <&> some digit <?> "integer"

word :: MyParser r u String
word = spaces <&> some alphaNum <?> "word"

test p = run getContents p userData
main = run input myParser userData

-- ##### Resources #####
-- GHC:        "http://www.haskell.org/ghc/docs/6.12.2/html/libraries/"
-- Parsec:     "http://hackage.haskell.org/package/parsec"
-- QuickCheck: "http://hackage.haskell.org/package/QuickCheck-2.1.0.3"
------------------------------- Templates end -------------------------------

input =
  getContents
--  readFile "./A-large-practice.in"
--  readFile "./A-small-practice.in"

userData =
  undefined -- Not used.
--  (0 :: Int)
--  (0 :: Integer)
--  (Set.empty :: Set.Set Int)
--  (Map.empty :: Map.Map Int Int)

line :: MyParser r u String
line = spaces <&> some alphaNum <?> "line"

-- ##### Solution #####
myParser :: MyParser r u ()
myParser = do
  [a :: Int]       <- count 1 integer
--  [a, b :: Int]    <- count 2 integer
--  [a, b, c :: Int] <- count 3 integer
  as <- count a line
--  bs <- count b line
--  cs <- count c line
  _TRACEP(a)
  _TRACEP(as)
--  _TRACEP(b)
--  _TRACEP(bs)
--  _TRACEP(c)
--  _TRACEP(cs)
  liftIOToParsec $ zipWithM_ f [1 .. ] (map solve as)
  where
    f i x = printf "Case #%d: %d\n" (i :: Int) (x :: Integer)
--    f i s = printf "Case #%d: %s\n" (i :: Int) (s :: String)

solve = const undefined