Lecture

関数型プログラミング Ch3 Haskellを使ってみよう

資料

Haskellを使ってみよう

ghci

前節では, Stackを利用した,プロジェクトの作成と実行に関して扱いましたが, Haskellにも対話環境が存在します. stack ghciコマンドを打つことで, Haskellの対話環境(REPL)が立ち上がります.

この節では,Haskellの基礎について学びますが,ghciの紹介も併せて,いくつかの基礎的な仕様については,ghci上で確認してみましょう.

終了

ghciではコマンドを:の後に入力します. ghciの終了コマンドは:qです.

 stack ghci
ghci>:q
Leaving GHCi.

コメントアウト

Haskellではコメントアウトは -- です. 複数行に渡る場合は {- -} で囲みます.

Haskellのプログラムを読んでいると --|--^ というタイプのコメントを良く見ますが, こちらはHaskellのドキュメント生成ライブラリにおいて, ドキュメント中に説明として記述するための記号です. またコメント中に >>> と記述することでテストが実装できるなどいろいろなものがありますが,本資料では扱いません.

ghci> -- コメント
ghci> {- コメント-}

複数行モード

ghci上で複数行のプログラムを書く場合には :{ :} でプログラムを囲います. 例えば,先程のフィボナッチ数のプログラムをghci上で実行する場合, 1行ずつ定義すると, 後から書いた節が先に書いた節を上書きしてしまい, 最後の fib n = fib (n-1) + fib (n-2) のみが残ります. この状態で fib 12 を呼び出すと, 基底ケース(fib 0fib 1)が失われているため n がマイナス方向に無限に再帰し続け, Stack Overflow エラーになります.

ghci> fib 0 = 1                       -- ここでは fib 0 = 1
ghci> fib 1 = 1                       -- fib 0 = 1 の定義が上書きされる
ghci> fib n = fib (n-1) + fib (n-2)   -- fib 1 = 1 の定義も上書きされる
ghci> fib 12
*** Exception: stack overflow

:{ :}で囲むことでひとまとまりの定義として認識されます.

ghci> :{
ghci| fib :: Int -> Int
ghci| fib 0 = 1
ghci| fib 1 = 1
ghci| fib n = fib (n-1) + fib (n-2)
ghci| :}
ghci> fib 12
233

なお,スクリプトの場合は,:{ :}なしでそのまま改行すれば問題ありません.

値の束縛

ghci 上で 名前 = 式 と書くと, 右辺の式の値に左辺の名前を 束縛(binding) することができます. 以後その名前を式の代わりに利用できます.

ghci> x = 1
ghci> x + 2
3

タプルやリストなど構造を持つ値に対しては, 値の形に合わせて複数の名前を一度に束縛することもできます. これを パターンマッチによる束縛 といい, 本章の後半でリスト・タプルを扱う際にも登場します.

ghci> (a, b) = (1, 2)
ghci> a
1
ghci> b
2

Haskellの 束縛 は, Pythonなど手続き型言語における 変数への代入 とは似て非なる概念です. 一度束縛した名前を別の値に再び結びつけ直すことはできず, 同じスコープで x = 1 の後に x = 2 と書くとエラーになります.

なぜそのような仕様になっているのか, 不変性・参照透過性・副作用といった関数型言語の根幹に関わる話題は次章の「変数(値の束縛)」で改めて詳しく扱います. ここではひとまず「名前 = 式 で名前を式の値に結びつけられる」程度の理解で先に進んでください.

データ型

型に関しては,かなり奥が深い,というよりHaskellの面白さは自分で型を作っていくことにあります. ただ,いきなりそれをすると,わけがわからなくなるのでまずは代数的データ型などには触れず以下の基礎的な型に関して説明します.

  • 数値型
    • 整数 (Int, Integer)
    • 実数 (Float,Double)
  • タプル
  • リスト (List)
  • 文字,文字列 (Char,String,Text)
  • 論理型(Bool)

Haskellにおいて,値のデータ型はある程度自動推論されますが,特定のデータ型を明示したい場合には,値の後ろに:: データ型をつけます.

ghci> 1 :: Int
1
ghci> 1 :: Double
1.0

ghciにおいて形の確認は:tあるいは:typeコマンドの後ろに確認したいデータを入力することで行えます.

ghci> :t 'c'
'c' :: Char
ghci> :type (1 :: Int)
(1 :: Int) :: Int

数値型

Haskellの基本的な数値型には以下の4つがあります. クラスに関しては後に扱うので,今はデータ型の更に大きな分類程度に考えておいてください.

クラス データ型 意味
Integer (整数) Int 固定長整数型
Integer (整数) Integer 多倍長整数型
Fractional (小数) Float 単精度浮動小数型
Fractional (小数) Double 倍精度浮動小数型

IntInteger整数, FloatDouble実数を表しています.

固定長/多倍長, 単精度/倍精度 というのはどういう意味でしょうか?

コンピューターでは,データはすべて01のいずれかを表すbitの集まりによって表現されます. ちなみに8bit1byte, 1024byte1Kbyteです.したがって,プログラミングで扱うデータに使用できるデータ量には制限があり,無限の長さの整数や小数を利用することはできません.

コンピューターの計算は主に中央演算処理装置(CPU)で行われますが,その計算過程でデータを一時的に記録するCPU内部の装置のことを汎用レジスタといい,現在では64bit以下の汎用レジスタを持った64bit CPUが良く利用されています.

現在一般的な64bit CPUにおいてHaskellは整数と小数を表すのに一般的に最大64bitの領域を確保します. したがって,整数では64bitで表せるデータ量(-9223372036854775808 ~ 9223372036854775807)を超えた整数を扱うことはできません.

ちなみにIntの最大値,最小値はghciで以下のように確認できます( 使用しているコンピューターによっては結果が変わる可能性があります).

ghci> minBound :: Int
-9223372036854775808
ghci> maxBound :: Int
9223372036854775807

最大(小)値を超えるとオーバーフローします.

ghci> 9223372036854775807 :: Int
9223372036854775807
ghci> (9223372036854775807 + 1) :: Int
-9223372036854775808

数値型の演算

Haskellにおける数値型の基本的な演算子は以下のように定義されています. 実数と整数で挙動が異なるものがあるので注意が必要です.

演算子には優先順位が設定されており,数字が大きいものから順に適用されます(最小0,最大9). また,式を()で囲むことで,その内部が優先的に計算されます.

計算 記号 優先順
足し算 + 6
引き算 - 6
掛け算 * 7
割り算 / 7
冪乗(整数) ^ 8
冪乗(実数) ** 8
ghci> 1 + 1
2
ghci> 2 - 1
1
ghci> 3 * 3
9
ghci> 9 / 3
3.0
ghci> 3 ^ 3
27
ghci> 3 ** 3
27.0
ghci> 3 ^ (1/2) -- エラー

<interactive>:7:3: error: [GHC-39999]

ghci> 3 ** (1/2)
1.7320508075688772
ghci> 2 * 3 + 1
7
ghci> 2 * (3 + 1)
8

これらは中置演算子として定義されていますが演算子を()で囲むことによって前置(逆ポーランド記法)で利用することができます.

ghci> (+) 3 4
7
ghci> (*) ((+) 3 4) 2
14
ghci> (*) 2 $ (+) 3 4
14

$は末尾の()を省略するための記号で, (*) x (y + z)(*) x $ y + z と書くことが出来ます. ただし, 中置演算子に対しては使えません.

また, 2引数関数として定義された前置の演算子は `` (バッククオート)で囲むことで, 中置演算子として利用できます.

計算 記号 優先順
整数除算 div 7
剰余 mod 6
ghci> 5 /2
2.5
ghci> div 5 2
2
ghci> 5 `div` 2
2
ghci> 5 `mod` 2
1

数値型の変換

Integral(整数)からFractional(小数)への変換は, fromIntegralを利用します.

ghci> fromIntegral (1 :: Int) :: Double
1.0
ghci> div 5 2
2
ghci> 2 ** (div 5 2)

<interactive>:6:1: error: [GHC-39999]
     Ambiguous type variable ‘a0’ arising from a use of ‘print’
      prevents the constraint ‘(Show a0) from being solved.
      Probable fix: use a type annotation to specify what ‘a0’ should be.
      Potentially matching instances:
        instance [safe] Show Version -- Defined in ‘Data.Version’
        instance Show Exception.ArithException
          -- Defined in ‘GHC.Exception.Type’
        ...plus 39 others
        ...plus 20 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
     In a stmt of an interactive GHCi command: print it
ghci> 2 ** (fromIntegral (div 5 2))
4.0

Fractional(小数)からIntegral(整数)への変換は,基本的に何かしらの切り捨てを実施します.

切り捨て関数名 意味
ceiling 小数点以下を切り上げる ceiling 3.2 → 4,
ceiling (-3.2) → -3
floor 小数点以下を切り下げる floor 3.8 → 3,
floor (-3.8) → -4
truncate 小数部分を単純に切り捨てる truncate 3.8 → 3,
truncate (-3.8) → -3
round 最も近い整数に丸める round 3.5 → 4,
round 3.4 → 3,
round (-3.5) → -4
ghci> 2 ^ (2 / 1)

<interactive>:8:3: error: [GHC-39999]
Could not deduce ‘Integral b0’ arising from a use of^
      --- 省略

ghci> 2 ^ (truncate (2 / 1))
4

Exercise CH3-1

数値演算 (div / mod / 累乗の優先順位)

以下の問題をREPLを使って自分で解いてみましょう. 問題自体は小学生でも解けますが,重要なのはHaskellの挙動を確認することです. どのように計算したかを併せて説明してください.

  • 飴が40個あります.7人で同じ数ずつ分けると1人分は何個で何個あまりますか?

  • 底辺5cm,高さ4cmの三角形の面積はいくつですか?

  • 2の8乗はいくつですか?

  • 累乗と掛け算の計算順序を丸括弧を使った計算で確かめてください.

回答例
-- 1人分の個数と余り: div で商, mod で余り
ghci> 40 `div` 7
5
ghci> 40 `mod` 7
5
-- → 1人 5個, 余り 5個

-- 三角形の面積 = 底辺 × 高さ ÷ 2
ghci> 5 * 4 / 2
10.0
-- → 10 cm²

-- 2の8乗
ghci> 2^8
256

-- 累乗と掛け算の優先順位: 累乗 (^) のほうが掛け算 (*) より強い
ghci> 2 * 3^2
18
ghci> (2 * 3)^2
36
ghci> 2 * (3^2)
18
-- → 括弧なしでは 2 * (3^2) と同じ

リスト

複数のデータをまとめる方法はいくつかありますが,データを1列に並べたList型は代表的なデータ型です. Haskellには配列(ArrayVector)もありますが,まずはListについて学習しましょう. リストの操作にはここで扱う以外にもリスト内包表記高階関数など様々なものがありますが,ここでは最も基本的ないくつかの機能のみに絞って,後ほど詳細を扱います.

Listはリストリテラル[]の中に要素を記入して,,(コンマ)で区切ることで宣言できます.

Haskellにおいて,リテラルとは,特定のデータ型の値を直接記述する構文のことを指します.

  • リストリテラル[]は,[]内の記述をリスト型として扱うリテラル

  • 数値を記入するとそれは数値型として扱われる数値リテラル

  • ""で囲まれた記述は文字列型として扱われる文字列リテラル

などがあります.

Haskellでは,自作したデータ型にリテラルを定めるなど様々な用法がありますが,ここでは扱いません.

ghci> [1,2,3]
[1,2,3]

[]のみで空のリストが生成されます.

ghci> []
[]

注意点として,HaskellはPythonなどの言語のようにダックタイピングが許されていないため異なるデータを単一のリストの要素に含めることはできません.

ghci> [1,2.0,3]
[1.0,2.0,3.0]
ghci> [1::Int,2::Double,3::Int]

<interactive>:22:9: error: [GHC-83865]
Couldn't match expected typeInt’ with actual typeDouble
In the expression: 2 :: Double
      In the expression: [1 :: Int, 2 :: Double, 3 :: Int]
      In an equation for ‘it’: it = [1 :: Int, 2 :: Double, 3 :: Int]

リストのデータ型は,要素のデータ型をリストリテラル[]で囲んだ形で表されます.

ghci> :type ([1::Int,2::Int])
([1::Int,2::Int]) :: [Int]
ghci> :type ['a','b','c']
['a','b','c'] :: [Char]

また,リストは先頭要素 : リスト によって宣言することも可能です. :cons 構築子といいます. 構築子の意味については後ほど代数的データ型の説明とともに扱います.

ghci> 1 : []
[1]
ghci> 1 : [2,3]
[1,2,3]
ghci> 1 : 2 : [3]
[1,2,3]

Haskellでリストを扱う際には,暗黙にxなどの単一のアルファベットが要素,xsなどの複数形がリストを表している場合が多くx:xsなどと記述してリストの最初の要素と残りのリストを表します.

詳細は後ほど扱いますが,束縛されたリストからパターンマッチによって値を取り出す場合によく利用されます.

ghci> x:xs = [1,2,3]
ghci> x
1
ghci> xs
[2,3]

リストの要素のインデックスによる取得は !!演算子を用いてxs !! インデックスの形で行います. インデックスは0から始まります. インデックスが超過した場合はエラーとなります.

ghci> [1,2,3] !! 0
1
ghci> [1,2,3] !! 1
2
ghci> [1,2,3] !! 2
3
ghci> [1,2,3] !! 3
*** Exception: Prelude.!!: index too large
CallStack (from HasCallStack):
  error, called at libraries/base/GHC/List.hs:1366:14 in base:GHC.List
  tooLarge, called at libraries/base/GHC/List.hs:1376:50 in base:GHC.List
  !!, called at <interactive>:37:9 in interactive:Ghci15

m~nまでの連続したリストを生成する場合には,[m..n]と記述します.これを数列表記といいます.

ghci> [1 .. 10]
[1,2,3,4,5,6,7,8,9,10]

コンマと併用することで階差数列などを表現することも可能です.

ghci> [1,3..10]
[1,3,5,7,9]

[x..]と終わりを指定しないことで,無限数列も作成できます. ghciでそのまま実行すると永遠に表示が止まりません(ctrl+Cで止まります). ここでは,[1,3,5,...]の10番目と100番目の値を取り出してみます.

ghci> [1,3..] !! 10
21
ghci> [1,3..] !! 100
201

Pythonなど言語では,値が宣言/生成されたタイミングでコンピュータがその値を評価する正格(strict)評価が一般的です. 一方HaskellはDefaultでは,実際にその値が呼び出された際に評価される遅延(lazy)評価を採用しており,それによりこのような無限の値を実現することができます. 正格評価で無限に値が続くリストを生成した場合, 生成した時点で永遠に計算が終わりませんが,遅延評価では無限のリストの中の具体的な値を利用するさいにその値が利用されます.

この機能はHaskellの大きな特徴の一つですが,一方でメモリリークや,速度の低下の原因になることがあります. したがって,ある程度大きなプログラムを書く場合には,正格評価と,遅延評価を明示的に切り替えることが推奨されています.

最初は気にする必要はありませんが,パッケージなどの提供するHaskellのデータ型には,strictなものとlazyなものの両方が用意されていることが多いので,違いを覚えておくと後々役に立ちます.

リスト同士の結合は++演算子によって行います.

ghci> [1] ++ [2,3]
[1,2,3]

リストの長さはlength 関数で取得できます.

ghci> length []
0
ghci> length [1,2,3]
3

Exercise CH3-2

リスト操作の基礎(数列表記, !!, cons, ++, 無限リスト)

リストの基本的な操作をghciで確認してみましょう.

  1. 数列表記 [m..n] を用いて, 1 から 100 までの整数のリストを生成してください.

  2. 階差数列表記 [m,n..o] を用いて, 1 から 99 までの 奇数のみ のリスト [1,3,5,...,99] を生成してください.

  3. 2 で生成した奇数のリストの 長さlength で求めてください.

  4. 2 で生成した奇数のリストの 20 番目 の要素を !! で取り出してください(インデックスは 0 始まりなので 20 番目は !! 19).

  5. cons 構築子 : のみを使って, リスト [1,2,3] を構築してください(リテラル [1,2,3] を直接書かないでください).

  6. リスト同士の結合 ++ を用いて, [1,2,3][4,5,6] を連結した結果の長さを length で確かめてください.

  7. 無限リスト [2,4..] (偶数の無限列) の 100 番目の値を取り出してください.

回答例
-- 1. 1から100までの整数
ghci> [1..100]
[1,2,3,4,5,6,7,8,9,10,11,12,...,99,100]

-- 2. 1から99までの奇数 (1,3,5,...,99)
ghci> [1,3..99]
[1,3,5,7,9,11,...,97,99]

-- 3. 奇数のリストの長さ (1から99までの奇数は50個)
ghci> length [1,3..99]
50

-- 4. 20番目の奇数 (0始まりなので !! 19)
--    一般項は 2n+1 なので n=19 のとき 39
ghci> [1,3..99] !! 19
39

-- 5. cons構築子のみで [1,2,3] を構築
--    最後は必ず空リスト [] で終わる
ghci> 1 : 2 : 3 : []
[1,2,3]

-- 6. ++ で連結した結果の長さ (3 + 3 = 6)
ghci> length ([1,2,3] ++ [4,5,6])
6

-- 7. 無限の偶数列 [2,4,6,...] の100番目 (0始まりなので !! 99)
--    一般項は 2(n+1) なので n=99 のとき 200
ghci> [2,4..] !! 99
200

タプル

Haskellではデータの組み合わせを表す方法として,後述の直積型がありますが,タプルも良く利用されます.タプルを利用するには要素を()(丸括弧)で囲い,,(コンマ)で区切ります. 要素数に制限はありません.

ghci> (1,2)
(1,2)
ghci> (1,2,3)
(1,2,3)

リストと同様に要素数の異なるタプルや,要素のデータ型の異なるタプルは別のデータ型として区別され,同一のリストなどに入れることはできません.

ghci> [(1,2),(1,2,3)]

<interactive>:60:8: error: [GHC-83865]
Couldn't match expected type: (a, b)
                  with actual type: (a0, b0, c0)
In the expression: (1, 2, 3)
      In the expression: [(1, 2), (1, 2, 3)]
      In an equation for ‘it’: it = [(1, 2), (1, 2, 3)]
Relevant bindings include
        it :: [(a, b)] (bound at <interactive>:60:1)

要素数が2つのリストに限定して,要素を取り出す関数 fst,sndが用意されていますが,値の取り出しはパターンマッチがよく利用されます.

ghci> fst (1,2)
1
ghci> snd (1,2)
2
ghci> (x,y) = (1,2)
ghci> x
1
ghci> y
2

文字列型

Haskellの文字列型は歴史的に少し複雑な状況になっており,PreludeにおけるString型の使い勝手があまり良くありません. なので, textパッケージの提供するText型を利用するのが一般的です. なので,後ほどTextを導入しますが,一旦String型に関して見てみましょう.

Haskellでは1文字を表す Char型と文字列を表すString型を区別し,Char''(シングルクォーテーション),String""(ダブルクオーテーション)で囲みます.

ghci> 'c'
'c'
ghci> :t 'c'
'c' :: Char
ghci> "String"
"String"
ghci> :t "String"
"String" :: String

Haskellにおける文字型Charのリスト[Char]の別名(型シノニム)です. 型シノニムは型に別の名前をつけることで,用途などを区別する機能です. 型シノニムは,以下のように, type 型シノニム = 元のデータ型という形で定義します.

type String = [Char]

したがって,String型にはListの演算が適用できます.

ghci> "String" !! 1
't'
ghci> '!' : "String"
"!String"
ghci> "!!" ++  "String"
"!!String"
ghci> length "String"
6

String型は非効率なため,現在ではあまり使われておらず,基本的にtextパッケージの提供する Data.Textを利用することが推奨されています.

package.yamldependenciesに以下のようにtextを追加します.

dependencies:
- base >= 4.7 && < 5
- text

スクリプトの最初に以下のように,記述することで文字列リテラル""Text型に利用できるようになります.

{-# LANGUAGE OverloadedStrings #-}
import Data.Text

{-# LANGUAGE OverloadedStrings #-}は言語拡張を表しており,Haskellの処理系に機能を追加する宣言です. OverloadedStringは文字列リテラルをTextなどの他の文字列を表すデータ型に適用できるようにする拡張です.

ghciで言語拡張を導入するには,:setに続けて -X言語拡張名を記述します.

ghci> :type "Text"
"Text" :: String
ghci> :set -XOverloadedStrings
ghci> import Data.Text
ghci> :type "Text"
"Text" :: Data.String.IsString a => a

論理型(Bool)

それが正しいか間違っているか判別できる文を命題といいます. 命題の結果を表すものとして真(正しい),偽(間違っている)という値を用います. 真と偽を併せて真偽値といいます.

例えば,1は2より大きいという命題は,間違っているのでとなります. 人間は必ず死ぬという命題は,今のところ不老不死の人間がいないのでです.

真偽値を表すデータ型としてBoolがあります. BoolTrue(真),False(偽)のいずれかです.

Haskellには命題の判定を行う関係演算子として,以下のようなものが準備されています.

記号 意味
> より大きい
>= 以上
< より小さい
<= 以下
== 等しい
/= 等しくない

数値などの大小関係を調べるときには,比較演算子 >,>=.<,<=を利用します. 演算子の左右に数値を書くと,結果に応じて真偽値が帰ってきます.

ghci> 1 > 2
False
ghci> 1 < 1.5
True

値が等しいか/等しくないかを判定するには,==/=を利用します.

ghci> 4 == 4
True
ghci> "cat" /= "cat"
False

命題と条件式

数理的な定義の内,そこで述べられた言説が,「真(TRUE)か偽(FALSE)のいずれかに分類可能とされるもの」を命題といい,条件が与えられた命題を条件式といいいます.

xに関する条件式を P(x) := ***Q(x) := *** と書き,***の部分に,命題が記述されます.

命題の記述には以下の論理演算子が用いられます.

  • \neg p(x):p(x) の否定

  • P(x) \lor Q(x)P(x)またはQ(x)

  • P(x) \land Q(x)P(x)かつQ(x)

これら \neg, \lor, \land の演算結果は,入力となる命題の真偽の組合せに対して一意に定まります. その対応関係を一覧化したものを真偽値表(真理値表) といいます. 以下では真をT(True),偽をF(False)として表記します.

P Q \neg P P \lor Q P \land Q
T T F T T
T F F T F
F T T T F
F F T F F

表から以下のことが読み取れます.

  • \neg P: P が真のときに偽, 偽のときに真となる(真偽の反転).
  • P \lor Q: PQ少なくとも一方が真のときに真となる.
  • P \land Q: PQ両方が真のときに限り真となる.

例えば,

  • 1は2より大きい かつ 2は0より大きい という命題は,2は0より大きいは正しいですが,1は2より大きいが間違っているので全体として,Falseです.

  • ネコは哺乳類である または ネコは鳥類であるという命題は ネコは鳥類であるが間違っていますが全体としてはTrueです.

また \Rightarrow によって,「ならば」という命題をあらわすことが出来ます.

  • P(x) \Rightarrow Q(x)P(x)ならばQ(x)
  • P(x) \Leftrightarrow Q(x): P(x) ならば Q(x) かつ Q(x) ならば P(x) (両方の含意が成り立つこと, すなわち同値)

このとき, p(x) \Rightarrow q(x) \Leftrightarrow \neg p(x) \lor q(x) となります.

これは真偽値表を用いて確認することができます. 以下の表で P \Rightarrow Q の列と \neg P \lor Q の列を比較すると,すべての行で値が一致しており,両者が論理的に等価であることが分かります.

P Q \neg P P \Rightarrow Q \neg P \lor Q
T T F T T
T F F F F
F T T T T
F F T T T

なお, P \Rightarrow QP が真で Q が偽のときに限り偽となり, それ以外は真となります. 特に P が偽のときは Q の真偽にかかわらず常に真となることに注意してください(これを空虚な真と呼びます).

Haskellにおいて TrueFalseをあらわすBool値は, AND(かつ),OR(または),NOTという演算で計算することができます(XORというのもあるが省略). HaskellではAND は &&, OR は ||, NOT は not という演算子が提供されています.

A,Bが命題だとして,A && Bは両方Trueのときに,Trueとなります. A || Bは片方どちらかがTrueのときにTrueとなります.

演算の結果は,それぞれ以下のようになります. これを真偽値表といいます. ここでは,最低限の例だけを紹介しますが,より深く理解したい人は論理学などの講義を受講しましょう.

命題Aの値 Bの値 A && B A || B
True True True True
True False False True
False True False True
False False False False
ghci> 1 > 2
False
ghci> 2 > 0
True
ghci> 1 > 2 && 2 > 0
False
ghci> 1 > 2 || 2 > 0
True

not は命題の否定を表しており TrueFalse,FalseTrueになります.notは命題の前に書きます.

ghci> 1 > 2
False
ghci> not (1 > 2)
True

Exercise CH3-3

偶数判定と論理演算 (∧, ∨, ⇒)

  1. ある値が偶数かどうかは,2で割った余りが0かどうかを判定することで判定できます. x=101,y=202として, 以下の命題の真偽を判定してください.
  • P(x) = xが偶数である

x \mod 2 \equiv 0

  • Q(y) = yが偶数

y \mod 2 \equiv 0

  • xが偶数かつyが偶数

P(x) \land Q(y)

  • xが偶数またはyが偶数

P(x) \lor Q(y)

  • x が偶数ならば yが奇数

P(x) \Rightarrow \neg Q(y)

  • x + y が奇数

(x + y) \mod 2 \not \equiv 0

回答例
ghci> x = 101
ghci> y = 202

ghci> -- xが偶数 (x を 2 で割った余りが 0)
ghci> p = x `mod` 2 == 0
ghci> p
False

ghci> -- yが偶数
ghci> q = y `mod` 2 == 0
ghci> q
True

ghci> -- xが偶数 かつ yが偶数
ghci> p && q
False

ghci> -- xが偶数 または yが偶数
ghci> p || q
True

ghci> --  x が偶数ならば yが奇数
ghci> not p || not q
True

ghci> -- x + y が奇数 (2 で割った余りが 0 でない)
ghci> (x + y) `mod` 2 /= 0
True
ce0f13b2-4a83-4c1c-b2b9-b6d18f4ee6d2