Haskellでreadlineを使う

cabalでreadlineをインストールするときにhomebrewで入れたreadlineを使う。

オプションを2つほど入れとく

cabal install readline \
--configure-option=--with-readline-libraries="/usr/local/Cellar/readline/6.2.2/lib" \
--configure-option=--with-readline-includes="/usr/local/Cellar/readline/6.2.2/include"

追記 120709

haskelineというものを教えてもらった。

これはよりhaskellっぽくかけて、高機能らしい。~/.haskelineでユーザー設定変えたりCtrl-Cの挙動を変えたりできる。

main :: IO ()
main = runInputT defaultSettings loop

ってやっておけばあとはreadlineと同じノリで書いていけるし、最初からこっちを使うようにすればいいかなと思った。

Invent With PythonのGuess the NumberをHaskellで

Guess the NumberをHaskellで

import System.Random

randomNumGen :: IO Int
randomNumGen = getStdRandom (randomR (1,20))

main :: IO ()
main = do
  print "Hello! What is your name?"
  myName <- getLine
  print $ "Well, " ++ myName ++ ", I am thinking of a number between 1 and 20."
  print "Take a guess."
  answer <- randomNumGen
  guessNumber 0 answer myName
      where guessNumber gt answer name =
              if gt > 4 
              then do print $ "Nope. The number I was thinking of was " ++ show answer
              else do
                  line <- getLine
                  let guess = read line :: Int
                  case (compare guess answer) of
                    LT -> print "Your guess is too low." >> guessNumber (gt + 1) answer name
                    GT -> print "Your guess is too high." >> guessNumber (gt + 1) answer name
                    EQ -> print $ "Good job, " ++ name ++ "! You guessed my number in " ++ (show gt) ++ " guesses!"

ユーザーからの入力を読み込んでIntに直すときに

line <- getLine
let guess = read line :: Int

ってやったけど、本当はどうやるのがいいんだろうか?

それから、名前と答えを関数で持ちまわるのがダサいが、こういう場合ReaderTとStateTどっち使うべきなんだろうか?

Write Yourself a Scheme in 48 Hours読了

Write Yourself a Scheme in 48 Hoursを読んでいるが、9,10章を流し読みしたので、一通り読み終わった。

丁度読んでる途中にWeb+DB PRESSを久しぶりに買った(Perl Meets Beats目当てで)んだけど、僕の中では「大規模コードリーディング」がヒットした。メタ知識重要ってことで。

ProductName WEB+DB PRESS Vol.69
大塚 弘記
技術評論社 / 1554円 ( 2012-06-23 )


Haskellのコードリーディングの場合は、手続きを追っかけると言うよりは、hoogleで型から関数の意味(すなわち単語の意味)を調べたり、文脈を考えながらセクションの意味を理解したり、クラスの意味を理解したりと、メタ知識をどんだけ仕入れるかが重要だったりするのでgoogle使用率が妙に高くなる感じ。

それから、本を読む本の技術が結構生きてくるのかなぁと。

ProductName 本を読む本 (講談社学術文庫)
J・モーティマー・アドラー
講談社 / 945円 ( 1997-10-09 )


もう少し効率的に読めるようにしたい。

Haskellのクロージャ

Write Yourself a Scheme in 48 Hoursを読んでいる。8章のクロージャ

定義がこうなっていた。

PrimitiveFunc ([LispVal] -> ThrowsError LispVal)
Func {params :: [String], vararg :: (Maybe String), 
      body :: [LispVal], closure :: Env}

スタックにつまないで環境をそのまま詰め込むのか。

そういえばRWHでも似たようなこと書いてあったような気がしたんだけど、どこだか忘れた。

ProductName Real World Haskell―実戦で学ぶ関数型言語プログラミング
Bryan O'Sullivan
オライリージャパン / 3990円 ( 2009-10-26 )


Haskellの場合はカリー化で束縛してもいいし、データコンストラクタで束縛してもいいのか。

Data.IORef

Write Yourself a Scheme in 48 Hoursを読んでいる。6,7章の状態をどうもたせるかのあたり。

6章のIOはSystem.IOにすれば動く

Data.IORefを使うのは初めてなんだけど、第20回 更新を高速化するためのSTモナドを読んだら、なんとなく方向性はわかった気がする。

要するにモナドにくるんで内部ではやりたい放題だけど、外からは素直に見えるようにするってことでしょ?

そういえば数学ガールのガロア理論で出てきた「閉じている」っていうのとモナドのfunctorのT: X -> Xってのは関係あるのかね?

ProductName 数学ガール ガロア理論 (数学ガールシリーズ 5)
結城 浩
ソフトバンククリエイティブ / 1995円 ( 2012-06-01 )


対象関数と射関数

モナドへの近道・Haskell からの寄道を読んでいて、

定義 3 (函手 functor) 函手 T : C → B は C の各対象 c に B の対象 T c を割当てる対象関数 (object function)T と,T(1c) = 1T c, T(g ◦ f) = T g ◦ T f (ただし,g ◦ f が C で定義されている) となるように C の 各射 f : c → c′ に B の射 T f : T c → T c′ を割当てる射関数 (arrow function) (同じく T と書く) からなる.

という説明を読んで、対象関数が型コンストラクタで、射関数がfmapのことかーとすんなり入ってきた。

ProductName すごいHaskellたのしく学ぼう!
Miran Lipovača
オーム社 / 2940円 ( 2012-05-23 )


Haskellで異なる型のリストをつくる

Write Yourself a Scheme in 48 Hoursを読んでいる。5章の評価2の最後あたり。

Schemeにはゆるく等価性を評価するequal?ってやつ。

(eqv? 2 "2")
#f
(equal? 2 "2")
#t

これを実装するために異る型のリストを作りmapMで順番に評価していく

data Unpacker = forall a. Eq a => AnyUnpacker (LispVal -> ThrowsError a)

unpackEquals :: LispVal -> LispVal -> Unpacker -> ThrowsError Bool
unpackEquals arg1 arg2 (AnyUnpacker unpacker) = 
             do unpacked1 <- unpacker arg1
                unpacked2 <- unpacker arg2
                return $ unpacked1 == unpacked2
        `catchError` (const $ return False)

-- or :: [Bool] -> Bool 
equal :: [LispVal] -> ThrowsError LispVal
equal [arg1, arg2] = do
    primitiveEquals <- liftM or $ mapM (unpackEquals arg1 arg2) 
                      [AnyUnpacker unpackNum, AnyUnpacker unpackStr, AnyUnpacker unpackBool]
    eqvEquals <- eqv [arg1, arg2]
    return $ Bool $ (primitiveEquals || let (Bool x) = eqvEquals in x)
equal badArgList = throwError $ NumArgs 2 badArgList

よろしくやってくれるデータ型を定義すればいいらしい。

ちょっとよくわからなかったので、適当な例をつくってみることにした。

{-# LANGUAGE ExistentialQuantification #-}

data HeteroEq = forall a. Eq a => HeteroEq (a -> Bool)

heterolist = [HeteroEq (3==), HeteroEq ("test"==), HeteroEq (True==)]

でもちょっとよくわからん。

追記 120701

これを読めばいいのかな

sofのStateモナドの添削がわかりやすかった

Working with the State monad in Haskell

collectUntil :: (s -> Bool) -> State s a -> State s [a]
collectUntil f s = do 
  s0 <- get
  let (a,s') = runState s s0
  put s'
  if (f s') then return [a] else liftM (a:) $ collectUntil f s

これはliftM使いすぎでbind使わなさすぎなコードらしく、次のようになおされてた。

collectUntil2 :: MonadState t m => (t -> Bool) -> m a -> m [a]
collectUntil2 f s = do
  s' <- get
  if f s' then return []              
  else do
    x  <- s
    xs <- collectUntil2 f s
    return (x:xs)

確かにこっちのほうが(State s)の配管を引っ張りだしてなくて見やすいかもなぁと思いつつ、bindを明示的に書いてみた。

collectUntil3 :: MonadState t m => (t -> Bool) -> m a -> m [a]
collectUntil3 f s = get >>= \s' -> 
                    if f s' 
                    then return []              
                    else s >>= \x -> collectUntil3 f s >>= \xs -> return (x:xs)

参考

ProductName すごいHaskellたのしく学ぼう!
Miran Lipovača
オーム社 / 2940円 ( 2012-05-23 )


HaskellでSchemeを実装する(エラーの導入)

Write Yourself a Scheme in 48 Hoursを読んでいる。4章のエラーチェッキングのあたり。

LispErrorを定義して、それをHaskellの組み込みのエラー関数で使えるようにする。

MonadErrorのインスタンスにすればthrowErrorとcatchErrorが使えるが、そのためにはEither型の左側をErrorクラスのインスタンスにする必要がある。

instance Error e => MonadError e (Either e) where
    throwError             = Left
    Left  l `catchError` h = h l
    Right r `catchError` _ = Right r

コード中ではここ。

type ThrowsError = Either LispError

instance Show LispVal where show = showVal
instance Show LispError where show = showError
instance Error LispError where
     noMsg = Default "An error has occurred"
     strMsg = Default

typeで別名をつけているのは取り回しを楽にするためかな?Error モナドを読んでも

type ParseMonad = Either ParseError

ってやってるし、エラーを取り扱う場合のお作法なのかなぁ。

モナド変換子を重ねまくれ

すごいHaskellのモナド変換子を理解したら読めばいいと思います、というか読むべき。超わかりやすかった。合体ロボ並に合成されて強そうな型に変換していく様は圧巻のゴーカイジャー(見てないからしらんけど)

スマイルプリキュアで例えると、「プリキュア・レインボーヒーリング」ですね(多分)。

最初に簡単な評価器からはじめる

eval0 :: Env -> Exp -> Value

という型から始めてモナドにする。

type Eval1  a = Identity a

次にエラーモナド変換子を重ねて

type Eval2  a = ErrorT String Identity a

環境であるReaderTを重ねる

type Eval3 a = ReaderT Env (ErrorT String Identity) a

続いて、状態を重ねる

type Eval4 a = ReaderT Env (ErrorT String (StateT Integer Identity)) a

ログ機能を導入して

type Eval5 q = ReaderT Env (ErrorT String
  (WriterT [String ] (StateT Integer Identity))) q

最後にお約束のIO

type Eval6 a = ReaderT Env (ErrorT String
  (WriterT [String ] (StateT Integer IO))) a

順番にテンポよく積み上がっていくので、読んでて悩むことなく最後の型まで到達できるので素晴らしい。英語も平易だし。

さらっと読んでみてよく分からなかったら、すごいHaskellに立ち戻ればいいでしょう。

ProductName すごいHaskellたのしく学ぼう!
Miran Lipovača
オーム社 / 2940円 ( 2012-05-23 )