「本物のプログラマはHaskellを使う」をこなしていく。 第ニ回

前回定義したrepeatedをもう一回使う。

 Prelude> let repeated f n = \x -> (iterate f x) !! n
 Prelude> :type repeated
 repeated :: (a -> a) -> Int -> a -> a
 Prelude> let f1 = \f n -> repeated f n 12
 Prelude> :t f1
 f1 :: Num a => (a -> a) -> Int -> a
 Prelude> let f2 = \f n -> repeated f n (12::Double)
 Prelude> :t f2
 f1 :: (Double -> Double) -> Int -> Double

:type とやると関数でもなんでも型がわかる。:type repeated したときに表示される a というのは特に指定なしでCharでもIntでも良い的な意味。次の f1 では、Num a となっているので、a の型はNumに固定される。f2 では、DoubleからDoubleへの変換を行う関数と、Intを引数に取り、Doubleを返す。と読み取れる。他にも :t (++"hogehoge") とか、:t String、:t ++ などをやって型というのに親しんでいく感じ。ココらへんは大体わかるので淡々とこなしていく。引き続き、Data.ByteString.Char8 のモジュールを読み込んで、[Char] と ByteString の違いをrepeated関数を使って見るんだけど、自分の知識不足故、ByteStringの何がどう便利なのかよくわからなかった。モジュールを読み込んで使う、という練習なのかな?


次は、Control.Exception モジュールを読み込んでfinallyを使ってみようというサンプル。型の「IO a」「IO()」というのを見るとモナドという正体不明の何かを想起させるため、アレルギーから理解を拒むようになる。それを見なかったことにして、粛々とサンプルを真似て打ち込んでいく。printThenAdd というのを定義するが、ここがなんかポイントっぽい気もする。return がモナドを返すというのもそれっぽい。が、ここでは気にせずに進もう。

 Prelude> let printThenAdd v = do {x <- v; print x; return (x+1)}
  Prelude> > :t printThenAdd
 printThenAdd :: (Num b, Show b) => IO b -> IO b
 Prelude> printThenAdd 1
 
 <interactive>:53:1:
    Non type-variable argument in the constraint: Num (IO b)
    (Use FlexibleContexts to permit this)
    When checking that ‘it’ has the inferred type
      it :: forall b. (Num b, Num (IO b), Show b) => IO b
 Prelude> printThenAdd (return 1)
 1
 2
 Prelude> :i return
 class Applicative m => Monad (m :: * -> *) where
   ...
   return :: a -> m a
   ...
         -- Defined in ‘GHC.Base’
 Prelude> :t return
 return :: Monad m => a -> m a


で、最後にrepeated と printThenAdd をhsファイルに書いて、それを読み込んでみる。なお、GHC7.10.1では、記事のとおりだとコンパイルが通らず、ちょっと変更が必要。こんな感じになる。

import Data.ByteString.Char8
import Control.Exception

repeat :: (a -> a) -> Int -> a -> a
repeat f n = \x -> (iterate f x) !! n

printThenAdd :: (Num a, Show a) => IO a -> IO a
printThenAdd v = do {x <- v; print x; return (x+1)}


今日のところは、こんなもんでおしまい。