「ふつうのHaskellプログラミング」読みます(7)
あいだ空きまくり。やばい。読むだけなら12章くらいまで読んだ。型とか型クラス、モナドとかもなんとなくわかった気だけはする。でも実際処理系とたわむれんとよくわからん。
というわけで順番にやる。8章、基本的な構文。
8.1 値としての関数
高階関数テスト。letつけてるのはghci内だから。コンパイル実行するときはいらない。
Prelude> let square n = n * n Prelude> map square [1, 2, 3, 4] [1,4,9,16]
でもsquareがここでしか使わない関数なら、いちいちsquareっていう名前を付けてあげて、その関数定義を後生大事に抱えてる必要も無いよね。というわけでHaskellでは無名関数というものを作ることができます。
Prelude> map (\x -> x * x) [1, 2, 3, 4] [1,4,9,16]
無名関数は複数のパターンマッチを列挙することができない以外は普通の関数定義と同様のパターンマッチができます。
タプルパターン。
Prelude> map (\(x,y) -> x - y) [(5,3), (1,4)] [2,-3]
データコンストラクタパターンとか
Prelude> import Char Prelude Char> map (\(x:xs) -> toUpper x : xs) ["hoge", "fuga", "moge"] ["Hoge","Fuga","Moge"]
8.2 関数合成
うん、数学者に好まれそうなHaskellらしさが見えてきた気がします。関数f, gを合成したfgとかよく使いますよね。単射がどうして全射がどうこうで全単射がほげほげとか。よく憶えてないけど。
まあそういう話は、考えたいときにじっくり考えればいいんですよね。手を動かしてみます。
Prelude> let numberOfLines = length . lines Prelude> numberOfLines "aaa\nbbb\nc\nd" 4 Prelude> let numberOfWords = length . words Prelude> numberOfWords "hoge fuga\nmoge piyo" 4 Prelude> import List Prelude List> let sortLines = unlines . sort . lines Prelude List> sortLines "hoge\nmoge\nfuga" "fuga\nhoge\nmoge\n"
じゅんちょー。
Prelude List> let tac = unlines . reverse . reverse . reverse . unlines <interactive>:1:20: Couldn't match expected type `String' against inferred type `Char' Expected type: [String] -> [String] Inferred type: [String] -> [Char] In the second argument of `(.)', namely `reverse . reverse . reverse . unlines' In the expression: unlines . reverse . reverse . reverse . unlines
なんだこれ。こうじゃねえのか。
Prelude List> let tac = unlines . reverse . reverse . reverse . lines Prelude List> tac "hoge\nfuga" "fuga\nhoge\n"
だいたい、unlinesの型って[String] -> String
じゃなかったか。
Prelude List> unlines "hogehoge" <interactive>:1:8: Couldn't match expected type `String' against inferred type `Char' Expected type: [String] Inferred type: [Char] In the first argument of `unlines', namely `"hogehoge"' In the expression: unlines "hogehoge"
そんなんできないよ。
8.3 部分適用
関数型言語の醍醐味の一つですね。Lisp系だとちょっとまわりくどくやることになるけど。MLとかHaskellだとむしろそれが基本。
関数の型をよく見て考えるとわかるかもしれないけど、MLとHaskellは複数の引数を受けとる関数は持たない。引数無しか、引数を1つとる関数しか定義できない。n個の引数を取っているように見えるのは1つの引数をとって、n-1個の引数を取っているように見える関数を返している。
じゃあ早速やってみよう。
Prelude> let addThree i j k = i + j + k Prelude> addThree 5 <interactive>:1:0: No instance for (Show (t -> t -> t)) arising from a use of `print' at <interactive>:1:0-9 Possible fix: add an instance declaration for (Show (t -> t -> t)) In the expression: print it In a 'do' expression: print it
できねえじゃんエラーでまくりだよ! と思ったけどちょっと待ってエラーメッセージを雰囲気で読む。「なんかprintできねえ値わたされたんすけど勘弁してくれ」とな。ためしに、そもそもaddThreeを渡してみる。
Prelude> addThree <interactive>:1:0: No instance for (Show (a -> a -> a -> a)) arising from a use of `print' at <interactive>:1:0-7 Possible fix: add an instance declaration for (Show (a -> a -> a -> a)) In the expression: print it In a 'do' expression: print it
ghciではそもそもたいていのschemeとかOCamlのインタプリタでできる、関数の表示ができないみたいですね。なんかの正確性のためにこの辺の利便性を捨てたのかな。これはちょっと不便。以下のような書き方をしても大丈夫なことから、部分適用できてんだなーということがわかりますね。
Prelude> ((addThree 5) 4) 3 12
えーと、この先読んだら上に書いた「2つ以上の引数を受けとる関数なんて無いよ!」みたいなこと書いてあった。
中置記法の場合はセクションといって特別な機能が用意されてるそうな。「特別な機能」てなんか嫌だな。
Prelude> 3 / 6 0.5 Prelude> (/ 6) 3 0.5 Prelude> 10^2 100 Prelude> (^ 2) 10 100
本では+を使ってたけど、+て引数の順序が結果に反映しないから例として微妙じゃないかな。
高階関数と組み合わせて。
Prelude> map (^ 2) [1, 2, 3, 4] [1,4,9,16] Prelude> filter ('\n' /= ) "hoge\nfuga\nmoge" "hogefugamoge"
zipLineNumberをちいさくする。zipLineNumberてなんだ。おぼえてねえ。
Prelude> let zipLineNumber xs = zip [1..] xs Prelude> zipLineNumber ["hoge", "fuga", "moge"] [(1,"hoge"),(2,"fuga"),(3,"moge")]
おもいだした。
Prelude> let zipLineNumber = zip [1..] Prelude> zipLineNumber ["hoge", "fuga", "moge"] [(1,"hoge"),(2,"fuga"),(3,"moge")]
うん。「Haskellには2つ以上の引数を取る関数は無い」ってことを考えると、自然に理解できる気がします。
8.4 ポイントフリースタイル
関数定義から変数が消えていく話。
firstNLinesの変数を減らす。
Prelude> let firstNLines n cs = unlines $ take n $ lines cs Prelude> firstNLines 2 "hoge\nfuga\nmoge" "hoge\nfuga\n" Prelude> let firstNLines n = unlines . take n . lines Prelude> firstNLines 2 "hoge\nfuga\nmoge" "hoge\nfuga\n"
fgrep.hsから変数を減らす。fgrep.hsどんなんだったか。
import System import List main = do args <- getArgs cs <- getContents putStr (fgrep (head args) cs) fgrep pattern cs = unlines (filter match (lines cs)) where match line = any prefixp (tails line) prefixp line = pattern `isPrefixOf` line
こんなんか。さすがにghci上でやるのはあきらめます。さて今まで見てきたものからして、まず関数定義の一番右にでてくる変数ってのは簡単に削れますよねたぶん。
import System import List main = do args <- getArgs cs <- getContents putStr (fgrep (head args) cs) fgrep pattern = unlines . filter match . lines where match = any prefixp . tails prefixp = pattern `isPrefixOf`
こんなんですね。
c:\hoge\docs\futkel\8>ghc fgrep.hs -o fgrep fgrep.hs:12:0: parse error (possibly incorrect indentation)
駄目だった。なんでだろうとしばらくわからんかった。こうだ。
import System import List main = do args <- getArgs cs <- getContents putStr (fgrep (head args) cs) fgrep pattern = unlines . filter match . lines where match = any prefixp . tails prefixp = (pattern `isPrefixOf`)
c:\hoge\docs\futkel\8>ghc fgrep.hs -o fgrep c:\hoge\docs\futkel\8>fgrep prefix <fgrep.hs match = any prefixp . tails prefixp = (pattern `isPrefixOf`)
できました。やはり中置記法は邪道ですね。isPrefixOfをわざわざ中置記法にしなければ、余計な括弧もいらなかったのです。prefixpの定義がこんだけ小さくなれば、いちいちprefixpなんて名前に束縛すんのも面倒ですよね。そのまんまmatch定義の中に書いちゃいましょう。
import System import List main = do args <- getArgs cs <- getContents putStr (fgrep (head args) cs) fgrep pattern = unlines . filter match . lines where match = any (isPrefixOf pattern) . tails
ついでにisPrefixOfを中置記法にするなどという黒魔術をやめました。
あと、where節を消します。where節で上の方で定義してる変数使うとか、結局グローバル変数みたいな駄目さありますよね。
import System import List main = do args <- getArgs cs <- getContents putStr (fgrep (head args) cs) fgrep pattern = unlines . filter (match pattern) . lines match pattern = any (isPrefixOf pattern) . tails
c:\hoge\docs\futkel\8>ghc fgrep.hs -o fgrep c:\hoge\docs\futkel\8>fgrep pattern <fgrep.hs fgrep pattern = unlines . filter (match pattern) . lines match pattern = any (isPrefixOf pattern) . tails
やった、ずいぶん短かくなりましたね。
ちなみに本には「型宣言は省略しない方がいいよ!」とありますね。個人的にはもうちょっと型推論の妙技に感じいりたい気分なので略せるだけ略してみようかなと。
8.6 練習問題
1 lstrip
Prelude> let lstrip = dropWhile (== ' ') Prelude> lstrip " hoge" "hoge"
2 rstrip
Prelude> let rstrip = reverse . lstrip . reverse Prelude> rstrip "hoge " "hoge"
3 strip
Prelude> let strip = rstrip . lstrip Prelude> strip " hoge " "hoge"
4 tail.hsをポイントフリースタイルで
こんなんかな。
main = do cs <- getContents putStr $ lastNLines 2 cs lastNLines n = unlines . takeLast n . lines takeLast n = reverse . take n . reverse
c:\hoge\docs\futkel\8>tail <tail.hs takeLast n = reverse . take n . reverse
できた。ちょっと忘れてた。やっべ。この章は基本ですね。重要重要。