Hatena::Grouphaskell

[ pred x | x <- "Ibtlfmm!ojllj" ] RSSフィード

2009-05-30

else if 22:37

ifのインデント関係は微妙だ。

以下はコンパイルが通るが、

a :: Maybe Int
a =
  if True
  then return 3
  else if False
  then return 6
  else return 4

以下はエラーになる。

a :: Maybe Int
a = do
  if True
  then return 3
  else if False
  then return 6
  else return 4

でもこれは通る。

a :: Maybe Int
a = do
  if True
    then return 3
    else if False
    then return 6
    else return 4

一貫性を重視するとこう。

a :: Maybe Int
a = do
  if True
    then return 3
    else if False
      then return 6
      else return 4

こういう場合case文で書くというイディオムがあるらしい。

a :: Maybe Int
a = do
  case () of
    _ | True -> return 3
      | False -> return 6
      | otherwise -> return 4

condに似たものを感じる。

(このcaseによる書き方を紹介しようと思って書き始めたが、インデントの深さでいうと3つ目のほうがいい気がしてきた)

トラックバック - http://haskell.g.hatena.ne.jp/illillli/20090530

2009-05-16

[]ジェネレータ 16:04

PythonRubyのジェネレータのような仕組みを提供するモナドを書いた。

YieldモナドがあればPauseモナドはいらない。

import PauseMonad

main :: IO ()
main = tracePauseT $ do
  liftIO $ print 1
  pause
  liftIO $ print 2
  pause
  liftIO $ print 3
  pause
  liftIO $ print 4
import YieldMonad

main :: IO ()
main = traceYieldT $ do
  liftIO $ print 1
  yield "hoge"
  liftIO $ print 2
  yield "fuga"
  liftIO $ print 3
  yield "foo"
  liftIO $ print 4
トラックバック - http://haskell.g.hatena.ne.jp/illillli/20090516

2009-05-14

[]WinGhci 20:56

f:id:illillli:20090514221134p:image:right:w180

ghciGUIフロントエンドを見つけた。コマンドプロンプトで直接GHCiを使うのと比べて特段プログラムに便利なわけではないが、ツールを登録したりフォントが変えられたりするのは地味にうれしい。それとセッションをまたがって入力の履歴が残るのと、ログをコピーしても矩形選択にならない。同梱のinstall.exeを実行するとwinghciが.hsファイルに関連付けされる。

[]jEdit 22:17

f:id:illillli:20090514221133p:image:right:w180

テキストエディタ。UIが英語のみなのが玉に瑕だが、Haskellを標準である程度ハイライトしてくれる。BufferTabsプラグインが便利。

StateT IO 21:45

本物のプログラマはHaskellを使う - 第6回 局所的な「状態」を利用するためのStateモナド:ITproの記事を見ながら手でいじった記録。

言うまでもないが、Haskellはオブジェクト指向ではない。純粋で関数型で遅延的だ。ということで、データを変換する関数と無限リストだけで禁欲的にプログラムを書いて、最後にmain以下のIOの嵐で鬱憤を晴らすのが最初に試す方法だろう。プログラマの趣味にもよるが、少なくとも僕はこれだと無理が出てくる。

「Stateモナドを使えば状態が扱える」という噂があるが、しばらくいじってみても結局関数の塊だとわかる(つまり、i++はできてもprintができない)。あと何だかrunStateの引数の順番が気に食わない。以下ではflipで対処したが、コードではwithStateと名前をつけたい。

Prelude Control.Monad.State> :m Control.Monad.State
Prelude Control.Monad.State> :i State
newtype State s a = State {runState :: s -> (a, s)}
 :
instance MonadState s (State s)
Prelude Control.Monad.State> :i MonadState
class (Monad m) => MonadState s m | m -> s where
  get :: m s
  put :: s -> m ()
 :
Prelude Control.Monad.State> :t runState get
runState get :: s -> (s, s)
Prelude Control.Monad.State> runState get 1
(1,1)
Prelude Control.Monad.State> flip runState 1 get
(1,1)
Prelude Control.Monad.State> flip runState 1 $ do get
(1,1)
Prelude Control.Monad.State> flip runState 1 $ do put 3; get
(3,3)
Prelude Control.Monad.State> flip runState 1 $ do put 3; x <- get; print x

<interactive>:1:38:
    Couldn't match expected type `State b a'
           against inferred type `IO ()'
    In the expression: print x
    In the second argument of `($)', namely
        `do put 3
            x <- get
            print x'
    In the expression:
          flip runState 1
        $ do put 3
             x <- get
             print x
Prelude Control.Monad.State> flip runState 1 $ do put 3; x <- get; modify (* 10); y <- get; return (x,y)
((3,30),30)

モナドのすべてでは、ここでStateの代わりにStateTというモナド変換子を使うとStateを使いながらIOができると謳う。

Prelude Control.Monad.State> :i StateT
newtype StateT s m a = StateT {runStateT :: s -> m (a, s)}
  :
Prelude Control.Monad.State> flip runStateT 1 $ do put 3; x <- get; print x

<interactive>:1:39:
    Couldn't match expected type `StateT b m a'
           against inferred type `IO ()'
    In the expression: print x
    In the second argument of `($)', namely
        `do put 3
            x <- get
            print x'
    In the expression:
          flip runStateT 1
        $ do put 3
             x <- get
             print x

IO ()をStateTの値にするにはliftかliftIOを使うそうだ。

Prelude Control.Monad.State> flip runStateT 1 $ do put 3; x <- get; liftIO $ print x
3
((),3)
Prelude Control.Monad.State> flip runStateT 1 $ do put 3; x <- get; liftIO $ print x; modify (* 10); y <- get; liftIO $ print y
3
30
((),30)

これでi++をしながらprintができる。

つまり、オブジェクト思考的なことにはStateT a IO bという型がつく。aは状態のデータ型で、上ではIntになっている。

id:illillli:20080228で実践してみたが、まだメモが雑然としている。そのうち整理したい。

問題:StateTはselfが暗黙 21:59

ひとつ問題がある。これでは一度に一つのオブジェクトしか操作できない。

get、put、modifyをもとに自身の状態を更新する関数を作ることはできるのだが、他の状態を変更しようとするとエラーになる。

Prelude Control.Monad.State> flip runStateT "hoge" $ flip runStateT 1 $ put "foo"
<interactive>:1:39:
    No instance for (Num [Char])
      arising from the literal `1' at <interactive>:1:39
    Possible fix: add an instance declaration for (Num [Char])
    In the second argument of `flip', namely `1'
    In the first argument of `($)', namely `flip runStateT 1'
    In the second argument of `($)', namely
        `flip runStateT 1 $ put "moga"'

もっとも内側のrunStateTで使う型が優先されるようだ。

liftとliftIOの違い 21:45

liftは変換子を使った数だけ重ねる必要があるが、liftIOでは何度合成しても一回ですむ。代わりにliftIOはIOの値にしか使えない。

HaskellWikiには、これをIO以外にも使えるようにしたMonadBaseというものがある。

Prelude> :m +Control.Monad.State Control.Monad.Identity
Prelude Control.Monad.State Control.Monad.Cont> flip runContT return $ flip runStateT 1 $ do put 3; x <- get; liftIO $ print x; modify (* 10); y <- get; liftIO $ print y
3
30
((),30)
Prelude Control.Monad.State Control.Monad.Cont> flip runContT return $ flip runStateT 1 $ do put 3; x <- get; lift $ print x; modify (* 10); y <- get; lift $ print y

<interactive>:1:23:
    Couldn't match expected type `ContT r m r'
           against inferred type `IO ((), b)'
    In the second argument of `($)', namely
        `flip runStateT 1
       $ do put 3
            x <- get
              lift $ print x
            modify (* 10)
            ....'
    In the expression:
          flip runContT return
        $   flip runStateT 1
          $ do put 3
               x <- get
                 lift $ print x
               modify (* 10)
               ....
    In the definition of `it':
        it = flip runContT return
           $   flip runStateT 1
             $ do put 3
                  x <- get
                    lift $ print x
                  ....
Prelude Control.Monad.State Control.Monad.Cont> flip runContT return $ flip runStateT 1 $ do put 3; x <- get; lift $ lift $ print x; modify (* 10); y <- get; lift $ lift $ print y
3
30
((),30)
トラックバック - http://haskell.g.hatena.ne.jp/illillli/20090514

2008-04-14

簡易ベンチマーク 00:26

> :m System.CPUTime
> let time m = do start <- getCPUTime; m; end <- getCPUTime; return $ end - start
> time $ do print 1
1
0
> :m +Control.Exception
> time $ evaluate $ show 100
0
> time $ evaluate $ length [1..1000000]
31250000000
> :m +Control.Monad
> time $ replicateM_ 100000 $ evaluate $ show 1000000000000
15625000000
> let timeSec = fmap (/ 10**12) . fmap fromIntegral . time
> time $ replicateM_ 100000 $ evaluate $ show 1000000000000
1.5625e-2

シンボルっぽいものを考える 00:56

Haskellはデータ構造が一通り豊富だが、不思議とシンボルがない。

自分のいうシンボルというのは、Schemeの'fooやRubyの:barのことで、以下のようなものだ。

  1. 比較が早い
  2. 手軽に生成できる
  3. 文字列から生成できる
  4. 文字列に変換できる
  5. 書き換えられない

このうち5はHaskellのデータ型ならほぼ大丈夫だ。

Data.Unique (1) (5)

Data.Uniqueに一見それらしい型があった。

data Unique
instance Eq Unique
instance Ord Unique
newUnique :: IO Unique
hashUnique :: Unique -> Int

問題は生成にIOがつくことと、文字列と相互変換ができないこと。

単純に文字列との組を作れば(4)を満たすが、IOのためにReadのインスタンスにもできない。

列挙型 (1?) (2) (3) (4) (5)

単純に考えるとこういう列挙子的なデータ型を作ればいい。

data Symbol = Foo | Bar | Baz deriving (Show, Read, Eq)

シンボルを受け取る関数は大体どんなシンボルを渡すべきか決まっているので、関数ごとにこういう型を作れば、型チェックがされてバグも少なくなる。

たいていはこの方法で用が済むのだが、異なる列挙子型どうしでは比較できない。(この方が便利な場合も多いが)

ハッシュ (1) (3) (5)

Data.HashTableにhashStringという関数がある。

hashString :: String -> Int32

もちろんハッシュテーブルに使うためなのだが、Int32をシンボルと見ると、これが文字列からシンボルを作る関数といえる。速度は計っていない。

(4)をするには辞書を自前で持つ必要がある。そのためShowのインスタンスにはできない。

辞書を作る場合、すぐそこにHashTableがあるが、操作にIOが付いているので元も子もない。Data.IntMapからIntMap Stringを作るのもいいかもしれない。

キャッシュすることを諦めれば、Uniqueと同様に文字列との組を作る選択肢がある。これが最もポータブルだろう。

また、IsStringクラスインスタンスにしておくと"hoge" :: Symbolのようなことができるので便利。

{-# OPTIONS -XOverloadedStrings #-}
module Symbol where

import Data.HashTable (hashString)
import Data.String
import Data.Int

data Symbol = Symbol { symbolName :: String, symbolID :: Int32 }

instance Eq Symbol where
  x == y = symbolID x == symbolID y

instance Show Symbol where
  show = symbolName

instance Read Symbol where
  readsPrec _ = map make . lex
    where
    make (word, rest) = (fromString word, rest)

instance IsString Symbol where
  fromString str = Symbol str (hashString str)

簡単なReadのインスタンス 01:42

忘れると思うので、上の記事から抜き出しておく。

Preludeのlexという関数はすごく便利。

instance Read Symbol where
  readsPrec _ = map make . lex
    where
    make (word, rest) = (fromString word, rest)

instance IsString Symbol where
  fromString str = Symbol str (hashString str)

ラップした。simply $ read "Foo" :: Fooのように使う。(Haskellに区切り方を任せているので、期待通りになるかはわからない)

module SimplyRead where

import Data.String

newtype R a = R { simply :: a }

instance (IsString a) => Read (R a) where
  readsPrec _ = map make . lex
    where
    make (word, rest) = (R $ fromString word, rest)

tt2008/04/22 10:50うちの環境だと :set +s で表示される時間と変わらないです > 簡易ベンチマーク

illillliillillli2009/05/14 20:50ほんとだ。GHCi本体についてるんですね。

トラックバック - http://haskell.g.hatena.ne.jp/illillli/20080414

2008-04-03

instance Arrow Reader 10:21

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

Readerというのは要するに(->)のnewtypeなので、簡単にArrowのインスタンスになる。

instance Arrow Reader where
  arr f = Reader f
  Reader f >>> Reader g = arr (f >>> g)
  first (Reader f) = arr (first f)
  second (Reader f) = arr (second f)
  Reader f *** Reader g = arr (f *** g)
  Reader f &&& Reader g = arr (f &&& g)

型だけ見ると、こういう対応があるようだ。

  • ask = returnA
  • asks = arr
  • local = (^>>)
  • mapReader = (^<<)
  • withReader = (^>>)

同じ調子でReaderTも何かにできないかと思ったが、Kleisliと型変数の順が違うだけということに気づいた。

newtype ReaderT r m a = ReaderT {
   runReaderT :: (r -> m a)
}
newtype Kleisli m a b = Kleisli {
   runKleisli :: (a -> m b)
}
トラックバック - http://haskell.g.hatena.ne.jp/illillli/20080403