いものやま。

雑多な知識の寄せ集め

GHCの使い方を調べてみた。(その5)

昨日までは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モナドでない場合
    1. その結果を変数itに束縛
    2. print関数で変数itを出力。
  • 評価した結果がIOモナドの場合
    1. その中身を変数itに束縛。
    2. 変数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> 

けど、型を考えれば分かる通り、nowUTCTime型なのに対し、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> 

今日はここまで!