kanetaiの二次記憶装置

プログラミングに関するやってみた、調べた系のものをQitaに移して、それ以外をはてブでやる運用にしようと思います。http://qiita.com/kanetai

プログラミングHaskell第4章(関数定義)まとめ

cons演算子「:」

  • 既存のリストの先頭に新しい要素を追加したリストを作成する
  • cons演算子は右結合
Prelude> :t (:)
(:) :: a -> [a] -> [a]
Prelude> 1 : [2, 3]
[1,2,3]
Prelude> 1 : (2 : (3 : [])) -- [1, 2, 3]はこれの略記
[1,2,3]
Prelude> 1 : 2 : 3 : [] -- 右結合なので括弧はいらない
[1,2,3]

関数{\leftrightarrow }演算子の変換

a -> b -> cのような関数を2項演算子のように使ったりその逆もできる。

関数をバックスラッシュで囲む

div 13 2    -- div :: Integral a => a -> a -> a
13 `div` 2  -- 2項演算子として使う

演算子を()で囲む(セクション)。

True && False   -- &&は2項演算子
(&&) True False -- (&&) :: Bool -> Bool -> Bool

関数定義

関数名 :: 関数の型
関数定義

ghciで関数定義す場合は、letをつけて、一行で定義するか:{:}でくくって複数行で書く。レイアウト規則に注意

Prelude> :{
Prelude| let alwaysTrue :: Bool -> Bool
Prelude|  alwaysTrue _ = True
Prelude| :}

<interactive>:18:2: parse error on input `alwaysTrue'
Prelude> :{
Prelude| let alwaysTrue :: Bool -> Bool
Prelude|     alwaysTrue _ = True
Prelude| :}
Prelude> alwaysTrue False
True

条件式(if文)

  • if文による定義では、else節は省略不可能
  • レイアウト規則にも注意
signum :: Int -> Int
signnum n = if n < 0 then -1
            else if n == 0 then 0
            else 1

ガード付きの等式

  • 最初のガードがTrueなら最初の結果が選ばれ、そうでないなら次のガードが評価される。
  • ガードotherwiseが使用できる。標準ライブラリで単にotherwise = True
  • ガード付きの等式による定義では、ガードotherwiseを必ずしも置く必要なないが、どのガードもTrueにならなければエラーが発生する。
signnum n | n < 0     = -1
          | n == 0    = 0
          | otherwise = 1

ガードと戻り値の順番が、下のような数学的な記述と逆なのを除くと結構わかりやすい構文ですね。

\( signnum(n)= \begin{cases} -1 & (n < 0) \\ 0 & (n = 0) \\ 1 & (otherwise) \end{cases} \)

パターンマッチ

値、変数、_(ワイルドカード)パターン

  • 同じ型の候補の中からマッチするものを返すよう関数定義できる。
  • 最初のパターンにマッチすれば最初の結果が選ばれ、そうでないなら次のパターンをみる。

ワイルドカードと変数の違い

(&&) :: Bool -> Bool -> Bool
True  && b = b 
False && _ = False
-- ◯ 同じ等式にワイルドカードが複数あるのはおk
True && True = True
_    && _    = False
-- × 同じ等式に同じ引数名があるのはNG
b && b = b
_ && _ = False
-- ◯ガード付き等式を使えばこのようにかける
b && c | b == c    = b
       | otherwise = False

タプル・パターン

fst :: (a, b) -> a
fst (x, _) = x
snd :: (a, b) -> b
snd (_, y) = y

リスト・パターン

-- 要素数が3のリストで頭が'a'ならTrue、そうでないならFalse
axxTest :: [Char] -> Bool
axxTest ['a', _, _] = True
axxTest _  = False
-- 先頭要素が'a`のリストならTrue、そうでないならFalse
firstATest :: [Char] -> Bool
firstATest ('a' : _) = True -- 関数適用は演算子より優先順位が高いため()でくくる必要がある
firstATest _ = False

{ n+k }パターン

  • { n }は整数、{ k }は正の整数定数
  • { n+k }パターンは{ k }以上の整数にしかマッチしない
  • 関数適用の方が優先順位が高いので()でくくる必要がある

Haskell 2010で仕様から削られてます。使わないほうが良いでしょう。

ghciで使う場合はXNPlusKPatternsオプションを使う

ghci -XNPlusKPatterns

ファイルにオプションを書けばコマンドラインオプションは不要

{-# LANGUAGE NPlusKPatterns #-}
pred :: Int -> Int
pred 0       = 0
pred (n + 1) = n
-- 負の数にはマッチしないのでpred (-1)はNG

where節

where節の直前で定義した関数内でwhere節で定義した局所関数や局所変数を使う事ができる。レイアウト規則に注意。

halve :: [a] -> ([a], [a])
halve xs = (take l xs, drop l xs)
         where l = length xs `div` 2
-- splitAt :: Int -> [a] -> ([a], [a])を使うと
halve xs = splitAt (length xs `div` 2) xs

{ \lambda }式(lambda expression)

バックスラッシュで{ \lambda }式(無名関数)が使用できる。

  • カリー化された関数の形式的な意味づけに利用できる
mul3 x y z = x*y*z
mul3 = \x -> (\y -> (\z -> x*y*z))
mul3 = (\x y z -> x*y*z) -- こう書くこともできる
  • 関数を返す関数定義に便利
-- const関数は、「引数が何であっても指定した定数 x を返す関数」を返す
const :: a -> b -> a
const x _ = x
-- 関数を返すことがわかりやすい書き方
const :: a -> (b -> a)
const x = \_ -> x
  • where節に書く局所関数を簡略記述できる
-- map :: (a -> b) -> [a] -> [b]
-- map関数は、リストの各要素に関数を適用したリストを返す
evens :: Int -> [Int]
evens n = map (\x -> x*2) [0..n-1]

セクション

{ \oplus }演算子とすると、式{ (\oplus ), (x\oplus ), (\oplus y) }をセクションという。

  • { (\oplus ) = \lambda x \rightarrow (\lambda y \rightarrow x \oplus y) }
  • { (x\oplus ) = \lambda y \rightarrow x \oplus y }
  • { (\oplus y) = \lambda x \rightarrow x \oplus y }

主な利用方法

  • 部分適用した関数などの定義
  • 演算子の型の宣言(演算子そのものは正しい式ではないので演算子\(\rightarrow\)関数変換する)
  • 他の関数に演算子を渡す