昨日まではGHCiでのロードとインポートについて説明した。
今日はGHCiでの式の評価と変数への束縛について。
式の評価
GHCiで式(expression)を入力すると、その式が評価され、結果が表示される。
例えば、次のような感じ。
Prelude> 1 + 2 3 Prelude> "Hello" "Hello" Prelude> [1..10] [1,2,3,4,5,6,7,8,9,10] Prelude> [(a, b) | a <- [1..5], b <- [1..5], a < b] [(1,2),(1,3),(1,4),(1,5),(2,3),(2,4),(2,5),(3,4),(3,5),(4,5)] Prelude> map (2^) [1..10] [2,4,8,16,32,64,128,256,512,1024] Prelude> let square = \x -> x^2 in square 10 100 Prelude>
なお、式を評価した結果の型がShowクラスのインスタンスでもIOモナドでもない場合、エラーが出る。
Prelude> \x -> x + 10 <interactive>:190:1: No instance for (Show (a0 -> a0)) (maybe you haven't applied enough arguments to a function?) arising from a use of ‘print’ In the first argument of ‘print’, namely ‘it’ In a stmt of an interactive GHCi command: print it Prelude>
これについては後述。
(エラーメッセージに出ているit
がポイント)
あと、式を評価した結果の型がIOモナドの場合、そのIOモナドが実行される。
Prelude> getLine >>= putStrLn Test -- ここは文字列を入力した Test Prelude>
変数への束縛
GHCiではIOモナドの中にいるような状態なので、<-
を使ってIOモナドの中身を変数に束縛したり、let
を使って非IOの式を変数に束縛したり出来る。
(この表現が正しいのか、まだよく分かってないけど・・・)
Prelude> input <- getLine Hello. -- ここは文字列を入力した Prelude> input "Hello." Prelude> x <- return 42 Prelude> x 42 Prelude> add <- return $ \a b -> a + b Prelude> add 1 2 3 Prelude> let y = 72 Prelude> y 72 Prelude> let times a b = a * b Prelude> 3 `times` 4 12 Prelude>
同じ名前の変数への束縛
同じ名前の変数へ束縛を行うと、以前束縛していたものは見えなくなるので、注意が必要。
例えば、以下のとおり:
Prelude> let x = 10 Prelude> x 10 Prelude> let x = 5 Prelude> x 5 Prelude>
変数it
への束縛
GHCiで式の評価を行うと、その結果はit
という変数へ束縛される。
例えば、以下のとおり:
Prelude> "Hello" "Hello" Prelude> it ++ ", Haskell!" "Hello, Haskell!" Prelude>
もし結果の型がIOモナドの場合には、その中身がit
に束縛されることになる。
Prelude> getLine Hello -- ここは文字列を入力した "Hello" Prelude> it ++ ", Haskell!" "Hello, Haskell!" Prelude>
さて、実はコンソールへの結果の出力は、この変数it
を介して行われている。
具体的には、以下のとおり:
- 評価した結果がIOモナドでない場合
- その結果を変数
it
に束縛 print
関数で変数it
を出力。
- その結果を変数
- 評価した結果がIOモナドの場合
- その中身を変数
it
に束縛。 - 変数
it
の型が、Showクラスのインスタンスで、かつ、()
ではないときに限り、print
関数で変数it
を出力。
- その中身を変数
このため、評価した結果がIOモナドでない場合には、その型がShowクラスのインスタンスになっていないと、print
関数で出力しようとしたタイミングでエラーになる、というわけ。
正直、なぜIOモナドの場合には出力するかどうかの条件判断を行っているのに、そうでない場合は条件判断を行っていないんだ、という感じなんだけど・・・
束縛された変数の一覧
束縛された変数の一覧は、:show bindings
コマンドで見ることが出来る。
Prelude> let x = 1 Prelude> let y = "hello" Prelude> let z = getLine >>= putStrLn Prelude> z Hi -- ここは文字列を入力した Hi Prelude> :show bindings x :: Num a => a = _ y :: [Char] = _ z :: IO () = _ it :: () = () Prelude>
見ての通り、変数it
も束縛されている。
<-
とlet
の違い
ここはちょっとややこしい話。
読み飛ばしてしまってもOK。
<-
を使うのとlet
を使うのの違いは、式が評価されるタイミング・・・らしいのだけど、実際のところ、基本的には違いがないと思っていいと思う。
例では次のようなものが挙げられていた:
Prelude> x <- error "help!" *** Exception: help! Prelude> let x' = error "help!" Prelude> x' *** Exception: help! Prelude>
<-
では正格評価が行われるので、束縛しようとしたときにエラーが発生するけど、let
では非正格評価が行われるので、束縛するだけならエラーが発生しない、と。
けど、そもそも束縛しようとしているものが異なっているので、この2つは同等になっていなく、比較できるものではない。
例えば、「右側が同じ」というだけで比較するなら、次のような例も考えられる:
Prelude> import Data.Time Prelude Data.Time> now <- getCurrentTime Prelude Data.Time> now 2016-01-24 00:02:29.386843 UTC Prelude Data.Time> now 2016-01-24 00:02:29.386843 UTC -- 束縛したタイミングの時刻のまま Prelude Data.Time> let now' = getCurrentTime Prelude Data.Time> now' 2016-01-24 00:03:07.922379 UTC Prelude Data.Time> now' 2016-01-24 00:03:10.810014 UTC -- 評価したタイミングで時刻が更新されている Prelude Data.Time>
けど、型を考えれば分かる通り、now
はUTCTime
型なのに対し、now'
はIO UTCTime
型で、この2つは全くの別物。
ここで、じゃあどちらもIO UTCTime
型を束縛しようとすると、次のようになる:
Prelude Data.Time> now'' <- return getCurrentTime Prelude Data.Time> now'' 2016-01-24 00:09:23.900219 UTC Prelude Data.Time> now'' 2016-01-24 00:09:26.600279 UTC -- 評価したタイミングで時刻が更新されている Prelude Data.Time>
見ての通り、違いは生まれない。
同様に、さっきのerror
関数の例を修正してみると、
Prelude> x' <- return $ error "help!" Prelude> x' <interactive>:103:1: No instance for (Show GHC.Prim.Any) arising from a use of ‘print’ In a stmt of an interactive GHCi command: print it Prelude>
となり、束縛したタイミングではエラーが出なくなっている。
(ただし、評価したタイミングでのエラーはちょっと異なる)
なので、基本的には違いがないと思っていていいと思う。
(一応、以下のような例も作れたけど・・・)
Prelude> a <- print "Hi!" >>= \_ -> return 1 "Hi!" Prelude> a 1 Prelude> let b = print "Hi!" >>= \_ -> return 1 Prelude> b "Hi!" 1 Prelude> :show bindings a :: Integer = 1 b :: Num b => IO b = _ it :: Integer = 1 Prelude>
今日はここまで!