練習問題

今日は目先を変えて練習問題をやろう。
入門Haskell―はじめて学ぶ関数型言語」 p.72 より。

①前ページの実装から,takeとdropに大きな値や負の値が入った場合の対処をしなさい。

前ページの実装とはこれ。Prelude の関数とかぶってはいけないので名前は変えてある。

mytake 0 _ = []
mytake n (x:xs) = x : mytake (n-1) xs

mydrop 0 xs = xs
mydrop n (x:xs) = mydrop (n-1) xs

mytake に大きな値を入れるとリストそのもの,負の値を入れると空リストが返るようにする。逆に mydrop に大きな値を入れると空リスト,負の値を入れるとリストそのものが返るようにする。

mytake _ [] = []
mytake 0 _ = []
mytake n (x:xs) | n < 0 = []
               | otherwise = x : mytake (n-1) xs

mydrop _ [] = []
mydrop 0 xs = xs
mydrop n (x:xs) | n < 0 = x:xs
                | otherwise = mydrop (n-1) xs 

もっとすっきり書けそうだけどまぁいいか。実行結果。

*Main> mytake 10 [0,1,2,3,4,5]
[0,1,2,3,4,5]
*Main> mytake (-2) [0,1,2,3,4,5]
[]
*Main> mydrop 10 [0,1,2,3,4,5]
[]
*Main> mydrop (-2) [0,1,2,3,4,5]
[0,1,2,3,4,5]

ふたつめ。

②take と drop を同時に実行する splitAt :: Int -> [a] -> ([a], [a]) があります。たとえば
splitAt 2 [1,2,3,4] — ([1,2], [3,4])
のように,結果のタプルの第1要素が take ,第2要素が drop になります。この splitAt を定義しなさい。また,take と drop を splitAt を使って定義し直しなさい。

まずは splitAt のほうから(名前は変えてある)。

mysplitAt _ [] = ([], [])
mysplitAt n xs = ((take n xs), (drop n xs))

結果。

*Main> mysplitAt 2 [1,2,3,4]
([1,2],[3,4])

よし。じゃ,これを使って mytake と mydrop を定義し直すと

mytake n xs = fst $ mysplitAt n xs

mydrop n xs = snd $ mysplitAt n xs

結果は

*Main> mytake 3 [1,2,3,4,5]
[1,2,3]
*Main> mydrop 3 [1,2,3,4,5]
[4,5]

もうひとつ。

③takeWhile は,(a -> Bool) -> [a] -> [a] という型です。takeと似ていますが,決まった数だけとるのではなく,要素を計算し たら真である限りtakeし,一度でも失敗したら残りは返しません。これを定義しなさい。

こんなんでどうかな。

mytakeWhile _ [] = []
mytakeWhile f (x:xs) | f x = x : mytakeWhile f xs
                     | otherwise = []
*Main> mytakeWhile (\x -> x > 0) [3,2,1,0,-1,-2,-3]
[3,2,1]
*Main> mytakeWhile (\x -> x < 0) [3,2,1,0,-1,-2,-3]
[]

ポイントフリースタイル

今日の一行 – ポイントフリースタイルを参考にして mytake から引数を消してみる。

mytake n xs = fst $ mysplitAt n xs
            ↓
mytake n xs = fst (mysplitAt n xs)
            ↓ 関数合成を使って xs を外に追い出す
mytake n xs = (fst . (mysplitAt n)) xs
            ↓ xs を消す
mytake n    = (fst . (mysplitAt n))
            ↓ 関数合成演算子を前置
mytake n    = (.) fst (mysplitAt n)
            ↓ もう一度,関数合成演算子を使って今度は n を追い出す
mytake n    = (((.) fst) . mysplitAt) n
            ↓ n を消す
mytake      = ((.) fst) . mysplitAt

結果。

*Main> mytake 2 [1,2,3,4]
[1,2]

おお!うまくいった!すごいな。
ポイントは関数合成とその演算子を前置するところだな。

んー,待てよ。単純にこうやったらどうだ?

mytake = fst . mysplitAt
*Main> :l mysplitAt.hs
Compiling Main             ( mysplitAt.hs, interpreted )

mysplitAt.hs:25:20:
    Couldn't match `(a, b)' against `t -> t1'
      Expected type: (a, b)
      Inferred type: t -> t1
      Expected type: Int -> (a, b)
      Inferred type: Int -> [a1] -> ([a1], [a1])
    In the second argument of `(.)', namely `mysplitAt'
Failed, modules loaded: none.

だめか。
そうか, (.) fst が関数を引数にとる関数で,これと mysplitAt を合成すると,mysplitAt の引数が外に追い出されるんだ。つまりこう。

mytake n xs = (((.) fst) . mysplitAt) n xs

で,引数が消える,と。