プログラミングHaskellを読む (2)

第2章 はじめの一歩

標準ライブラリ:Prelude

  • GHCiは起動時に標準ライブラリはPrelude.hsを読み込む。
    • プロンプトにPreludeと出るのはそのため。
  • 各種演算子も当然のことながら関数で、Prelude.hsで定義されている。
    • 整数の割り算は"`div`"*1浮動小数点の割り算は"/"。
    • 累乗は"^"
    • 優先順位は数学と同様、累乗>乗算除算>加算減算
      • ToDo: どうやって優先順位を指定しているのか、この先読んで確認する。
    • 累乗のみ右結合
  • リスト系の関数
    • head, tail, !!, take, drop, length, sum, product, ++, reverse

リスト系の関数のオレオレ実装

頭の体操がてら、自力で実装してみたので置いておきます。

-- head
myhead :: [a] -> a
myhead [] = error "empty list"
myhead (a : b) = a

-- tail
mytail :: [a] -> [a]
mytail [] = error "empty list"
mytail (a : b) = b

-- last (計算量多い)
mylast :: [a] -> a
mylast [] = error "empty list"
mylast (a : []) = a
mylast (a : b) = mylast b

-- init (計算量多い)
myinit :: [a] -> [a]
myinit [] = error "empty list"
myinit (_ : []) = []
myinit (a : b) = a : myinit b

-- reverse (計算量ヤバい)
myreverse :: [a] -> [a]
myreverse [] = []
myreverse a = mylast a : myreverse (myinit a)

-- take (負値を指定すると、リストがそのまま出てしまう)
mytake :: Int -> [a] -> [a]
mytake _ [] = []
mytake 0 _ = []
mytake a (hd : tl) = hd : mytake (a-1) tl

-- length (アキュームレータ 使わなくても 出来るのね (字余り))
mylength :: [a] -> Int
mylength [] = 0
mylength (_ : tl) = 1 + mylength tl

-- null
mynull :: [a] -> Bool
mynull [] = True
mynull _ = False

-- mydrop
mydrop :: Int -> [a] -> [a]
mydrop 0 b = b
mydrop _ [] = []
mydrop a (hd : tl) = mydrop (a-1) tl

-- sum
mysum :: Num a => [a] -> a
mysum [] = 0
mysum (hd : tl) = hd + sum tl

-- product
myproduct :: Num a => [a] -> a
myproduct [] = 1
myproduct (hd : tl) = hd * myproduct tl

-- elem (Trueの時点で止まらないのが無駄)
myelem :: Eq a => a -> [a] -> Bool
myelem _ [] = False
myelem a (hd : tl) = (a == hd) || myelem a tl

-- combine (組み込みの++)
mycombine :: [a] -> [a] -> [a]
mycombine [] b = b
mycombine (hd : tl) b = hd : mycombine tl b

-- index (組み込みの!!)
myindex :: [a] -> Int -> a
myindex [] _ = error "invalid index"
myindex (hd : tl) 0 = hd
myindex (hd : tl) a = myindex tl (a-1)

-- max
mymax :: Ord a => a -> a -> a
mymax a b = if a > b then a else b

-- min
mymin :: Ord a => a -> a -> a
mymin a b = if a < b then a else b

-- maximum (必殺ワザfoldr)
mymaximum :: Ord a => [a] -> a
mymaximum [] = error "empty list"
mymaximum (hd : tl) = foldr mymax hd tl

-- minimum
myminimum :: Ord a => [a] -> a
myminimum [] = error "empty list"
myminimum (hd : tl) = foldr mymin hd tl

GHCiの特殊コマンド

Hugsも同じ感じみたいです。

コマンド 動作
:load, :l 指定したファイルのロード
:reload, :r 現在のファイルをリロード
:cd, :c 作業ディレクトリの変更
:type expr, :t exprの型を見る
:quit, :q GHCi終了
:help, :h, :? ヘルプ
:!コマンド シェルのコマンドを実行

他にも色々あります。

プログラムの基本

  • 関数
    • [a-z][a-zA-Z0-9_']*
  • キーワード
    • case, class, data, ...色々
  • Haskellの慣習
    • リストが引数の時は最後にsをつける。
    • ex. 文字列のリストcss ([[char]])
  • レイアウト規則
    • Pythonと同様インデントでグループ化
    • 波括弧+;でもいい。({ a; b })
  • コメント
    • インライン: "-- " (スペースが重要)
    • ブロック: "{- -}"*2
  • 日本語文字コード: UTF-8

練習問題

1. 括弧を付ける

数学と同じ。

2 ^ 3 * 4 = ((2 ^ 3) * 4)
2 * 3 + 4 * 5 = ((2 * 3) + (4 * 5))
2 + 3 * 4 ^ 5 = (2 + (3 * (4 ^ 5)))
2. 例題を実行

しました。

3. エラーを修正
N = a `div` length xs
    where
      a = 10
     xs = [1,2,3,4,5]

「関数の先頭は小文字」と「where内のインデント」かな。

n = a `div` length xs
    where
      a = 10
      xs = [1,2,3,4,5]
4. 空でないリストを受け取るlastを定義。他の定義も。
-- 上記の方法(パターンマッチはまだなので不正解?)
mylast (a : []) = a
mylast (a : b) = mylast b

-- indexを利用
mylast2 a = a !! (length a - 1)

-- dropを利用
mylast3 a = drop (length a - 1) a !! 0

-- reverse&head
mylast4 a = head (reverse a)

headをtake 1にするとか、他にもバリエーションは色々作れそうです。

5. 空でないリストから末尾を削るinitを定義。
-- 上記の方法(パターンマッチはまだなので不正解?)
myinit (_ : []) = []
myinit (a : b) = a : myinit b

-- takeを利用
myinit2 a = take (length a - 1) a

-- reverse&tail&reverse
myinit3 a = reverse (tail (reverse a))

-- 合成関数化(おまけ)
myinit4 a = (reverse . tail . reverse) a

*1:少数以下切り捨て

*2:なんか可愛い