SE情報技術研究会’s blog

http://se-info-tech.connpass.com

2015-12-22 『ふつうのHaskellプログラミング』 もくもく読書会(第4章) の振り返り

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

第4章 Haskellの基礎(3) モジュールと総合演習

注意事項

この本が書かれた後にHaskellが新しくなりimportするモジュールが変わっているのでimportする際に注意

  • System → System.Environment
  • List → Data.List

echo.hs

モジュール

  • Haskellプログラムはモジュールという単位で分割されている
  • 関数や変数や型の集まり
  • javaでいうパッケージのようなもの
  • 他のモジュールで定義されている関数を使いたい場合はimport宣言が必要
  • getArgs とも System.Environment.getArgsとも書ける

Mainモジュール

  • main変数が属するモジュール
  • モジュールの宣言を何も書かないとファイル全体がMainモジュールとなる

Preludeモジュール

  • 基本的な型や関数が定義されたモジュール
  • すべてのモジュールは暗黙のうちにPreludeモジュールをインポートしている

unwords関数

unwords :: [String] -> String
  • 文字列のリストの各要素を空白を入れて連結する関数
  • 例: ["abc", "de", "fg"]"abc de fg"

getArgs関数

getArgs :: IO[String]
  • System.Environmentの関数
  • プログラムのコマンドライン引数を読み込むアクション
コメント

この本だとunwords関数getArgsアクションとあり、「関数」と「アクション」に定義の違いがあるのか気になる。 ちなみに「getArgs関数」と書いてある箇所もある。
→ アクションについてはp26に、11章で改めて説明するとある

頂いたコメントのように下記の点を押さえておけば現状はOK。

p26の説明通り、評価するとなぜかIOなどの副作用ができる。 つまり、「引数と返り値のみの関数とは違うものですよ。」ということだけ押さえて

fgrep.hs

  • 特定の文字列を含む行だけ抜き出すコマンド
  • コマンドライン引数と標準入力を受け取る
> fgrep ar < USA-states.txt
DE    Delware
MD    Maryland
…略

このようにUSA-states.txt内で「ar」を含む行が出力される

  • Delw"ar"e
  • M"ar"yland

head関数

  • リストの最初の要素を返す関数
  • 空リストの場合はエラー
head :: [a] -> a

tail関数

  • リストの最初の要素を除いたリストを返す
  • 空リストだとエラー

filter関数

  • 引数に渡したリストの要素が同じく引数の関数でtrueを返すもののみで構成されたリストを返す
filter :: (a -> Bool) -> [a] -> [a]

例:リストを奇数のみにする

> filter (\x -> x mod 2 == 1) [1,2,3,4,5]
[1, 3, 5]

fgrepの定義

fgrep :: String -> String -> String
fgrep pattern cs = unlines $ filter match $ lines cs
  where
    match :: String -> Bool
    match line = any prefixp $ tails line
    
    prefixp :: String -> Bool
    prefixp line = pattern `isPrefixOf` line
  • fgrep関数の第1引数patternコマンドラインで指定した文字列
  • fgrep関数の第2引数csは標準入力で読み込んだ文字列

  • lines cs
    → csの複数行の文字列を行ごとのリストにする

  • filter match $ lines cs
    → リストの各行をmatch関数に渡して結果がTrueのもののみのリストにする
  • match関数内のtails line
    → 受け取った行の文字列をtails関数を使って下記のような文字列のリストをつくる
> tails "abcde"
["abcde", "bcde", "cde", "de", "e", ""]
  1. any prefixp $ tails line
    → tails関数の要素をprefixp関数に渡して評価し、Trueになればany関数がTrueを返す
  2. prefixp関数内のpattern `isPrefixOf` line
    → patternの文字列がlineの開始文字列と一致すればTrue
  3. unlines $ filter …略
    → unlines関数でfilterの結果のリストをひとつの文字列にする
pattern `isPrefixOf` line

は下記と同じ

isPrefixOf pattern line

where節

  • その式だけに有効な定義を導入するための構文
  • 定義毎に改行が必要
  • 式とwhere句およびwhere句と定義1は改行があってもなくてもよい
where 定義1
         定義2
         定義3
         …略

tails関数

  • Data.Listモジュールの関数
tails :: [a] -> [[a]]

例:

> tails "abcde"
["abcde", "bcde", "cde", "de", "e", ""]

any関数

  • リストの要素を検証して、どれか一つでもTrueを返せばTrueを返す。
  • 全てがFalseならFalseを返す
any :: (a -> Bool) -> [a] -> Bool

isPrefixOf関数

  • Data.Listモジュールの関数
  • 指定したリスト(pattern)が調査対処のリスト(line)
isPrefixOf :: (Eq a) => [a] -> [a] -> Bool

練習問題

問1と問2

  • 解答の内容には特に問題なし
  • いきなりlines =<< getContentsが出てくるけど、どこかで説明が出ていた?