Diese Präsentation wurde erfolgreich gemeldet.
Die SlideShare-Präsentation wird heruntergeladen. ×

すごいHaskell読書会 第六章 発表資料

Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige

Hier ansehen

1 von 62 Anzeige

Weitere Verwandte Inhalte

Diashows für Sie (20)

Andere mochten auch (20)

Anzeige

Ähnlich wie すごいHaskell読書会 第六章 発表資料 (20)

Anzeige

Aktuellste (20)

すごいHaskell読書会 第六章 発表資料

  1. 1. すごいH たのしく学ぼう!
 第6章 モジュール twitter: @wrist facebook: ohashi.hiromasa
  2. 2. 自己紹介 • twitter ID: @wrist (手首) • http://hiromasa.info • 岐阜(高校) → 名古屋(大学) → 大阪(社会人) • 現在社会人3年目 • 某メーカーにて音響信号処理の研究開発やってます
  3. 3. (宣伝)大阪PRML読書会 • 機械学習の勉強会を主催してます • 現在二本立て • Pythonによるデータ分析入門 • PRML読書会 • 詳しくは「大阪 PRML読書会」 でググってください • 次回6/22(日)
  4. 4. 「第6章:モジュール」の概要 • モジュール(概要、インポート方法) • 標準モジュールの関数で問題を解く • 単語頻度カウント • リストの包含判定 • 暗号生成・解読 • 正格な左畳み込み
  5. 5. 概要続き • かっこいい数を見つけよう • Maybe, Just, Nothing • 連想リスト • タプルを用いた表現 • Data.Map • モジュールの作成 • 階層型モジュール
  6. 6. モジュールとは • いくつかの関数や型、型クラスなどを定義したファイル • Haskellのプログラムはモジュールの集合 • 定義したものは外部へエクスポート可能
  7. 7. コードを複数モジュールに分ける利点 • 多くの異なるプログラムから使える • 疎結合となることで再利用性が高まる • 管理しやすい
  8. 8. Haskellの標準ライブラリ • 複数のモジュールに分割 • モジュールごとに共通の目的を持つ関数、型が定義 • ex. リスト操作、並行プログラミング、複素数 • これまでの章のすべての関数、型、型クラスは
 全てPreludeというモジュールの一部
  9. 9. GHCiのプロンプト • デフォルトだと”Prelude> “となっているが、
 これはPreludeモジュールを使っているという意味 • GHCi上で”:m + Data.List”などと他のモジュールをイン ポートすると、表示は”Prelude Data.List> “に変化 • :show promptで表示、:set promptで変更可能
  10. 10. GHCi上では:show importsで importしたモジュールが見れる GHCi> :m + Data.List Data.Map GHCi> :show imports import Prelude -- implicit import Data.List import Data.Map
  11. 11. モジュールのインポート • import ModuleName でインポート • 例: リスト中の一意な要素を数える関数の作成 • Data.Listに含まれるnub関数を使用 • nub: Listから重複を取り除く関数 import Data.List ! numUniques :: (Eq a) => [a] -> Int numUniques = length . nub
  12. 12. Hoogle • Haskellの検索エンジン • 関数名、モジュール名、型シグネチャから検索可能
  13. 13. GHCiでのインポート • ghci> :m + Data.List • ghci> :m + Data.List Data.Set -- 複数モジュール • モジュールをインポートするスクリプトを
 ロードした場合は:m +を使う必要はない
  14. 14. 特定の関数のみインポート • 特定の関数のみインポート • import Data.List (nub, sort) • 特定の関数以外をインポート • import Data.List hiding (nub) -- nub以外をインポート
  15. 15. 修飾付きインポート(qualifiedインポート) • 名前の競合を避けるための機構 • import qualified Data.Map • Data.Mapのfilter関数を使う場合
 Data.Map.filterとアクセスする必要有 • import qualified Data.Map as M • M.filterでアクセスできる
  16. 16. .演算子 • 二つの用法 • 修飾付きインポートしたモジュールの関数の参照 • 関数合成演算子 • モジュール名と関数名の間に空白を空けずに置いた場合 インポートされた関数、そうでなければ関数合成
  17. 17. 標準モジュールの関数で問題を解く • 標準ライブラリのモジュールに含まれる関数の紹介 • 実例を見ていく
  18. 18. 単語を数える • 文字列に対して単語をカウントしたい • Data.Listのwords関数: 空白で単語を区切る GHCi> words "hey these are the words in this sentence" ["hey","these","are","the","words","in","this","sentence"] GHCi> words "hey these are the words in this sentence" ["hey","these","are","the","words","in","this","sentence"] • Data.Listのgroup関数: 隣接要素をグルーピング GHCi> group [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7] [[1,1,1,1],[2,2,2,2],[3,3],[2,2,2],[5],[6],[7]]
  19. 19. 要素が隣接していない場合 • あらかじめソートすることで解決 • Data.Listのsort関数を利用 GHCi> group ["boom", "bip", "bip", "boom", "boom"] [["boom"],["bip","bip"],["boom","boom"]] GHCi> sort [5,4,3,7,2,1] [1,2,3,4,5,7] GHCi> sort ["boom", "bip", "bip", "boom", "boom"] ["bip","bip","boom","boom","boom"]
  20. 20. 文字列を単語リストに分割しソートしグルーピ ングし単語と出現回数のタプルに分ける関数 GHCi> :t words words :: String -> [String] GHCi> :t sort sort :: Ord a => [a] -> [a] GHCi> :t group group :: Eq a => [a] -> [[a]] GHCi> :t (ws -> (head ws, length ws)) (ws -> (head ws, length ws)) :: [a] -> (a, Int) import Data.List ! wordNums :: String -> [(String,Int)] wordNums = map (ws -> (head ws, length ws)) . group . sort . words GHCi> wordNums "wa wa wee wa" [("wa",3),("wee",1)]
  21. 21. 関数合成なしで書く • うわあ括弧だらけ! import Data.List ! wordNums :: String -> [(String,Int)] wordNums xs = map (ws -> (head ws, length ws)) (group (sort (words xs)))
  22. 22. 干し草の山から針を探す • 2つのリストを受け取り、1つ目のリストが2つ目のリストの どこかに含まれているかを調べる • [3,4]は[1,2,3,4,5]に含まれているが[2,5]は含まれていない • 検索対象のリストをhaystack(干し草の山)、
 検索したいリストをneedle(針)と呼ぶ
  23. 23. 方針 • Data.Listのtails関数を使いリストの全tailを取得 ! • Data.ListのisPrefixOfを使い
 2つ目のリストが1つ目のリストで始まっているかを調べる ! • Data.Listのanyで要素のどれかが述語を満たすかを調べる GHCi> tails “party" => ["party","arty","rty","ty","y",""] GHCi> tails [1,2,3] => [[1,2,3],[2,3],[3],[]] GHCi> "hawaii" `isPrefixOf` "hawaii joe” => True GHCi> "haha" `isPrefixOf` “ha" => False GHCi> "ha" `isPrefixOf` “ha" => True GHCi> any (> 4) [1,2,3] => False GHCi> any (=='F') "Frank Sobotka” => True GHCi> any (x -> x > 5 && x < 10) [1,4,11] => False
  24. 24. 組み合わせる • Data.ListにisInfixOfという同じ動作の関数が!
 ちくしょう! import Data.List ! isIn :: (Eq a) => [a] -> [a] -> Bool needle `isIn` haystack = any (needle `isPrefixOf`) (tails haystack) GHCi> "art" `isIn` "party" True GHCi> [1,2] `isIn` [1,3,5] False
  25. 25. シーザー暗号サラダ • シーザー暗号でメッセージを暗号化 • 文字列をアルファベット上で一定の数だけシフト • Unicode文字全体に対するものが作れる • Data.Charの関数を使用(ord関数, chr関数) GHCi> :m + Data.Char GHCi> ord ‘a' => 97 GHCi> chr 97 => 'a' GHCi> map ord "abcdefgh" => [97,98,99,100,101,102,103,104]
  26. 26. シフトする数と文字列を受け取り文字列中の各文字を
 アルファベット上で指定された数だけ前方向にシフトする関数 • encode関数 import Data.Char ! encode :: Int -> String -> String encode offset msg = map (c -> chr $ ord c + offset) msg ! -- (chr . (+ offset) . ord)でも可 GHCi> encode 3 "hey mark” => "kh|#pdun" GHCi> encode 5 "please instruct your men” => "uqjfxj%nsxywzhy%~tzw%rjs" GHCi> encode 1 "to party hard” => "up!qbsuz!ibse"
  27. 27. decode関数 import Data.Char ! encode :: Int -> String -> String encode offset msg = map (c -> chr $ ord c + offset) msg ! decode :: Int -> String -> String decode shift msg = encode (negate shift) msg GHCi> decode 3 "kh|#pdun" "hey mark" GHCi> decode 5 "uqjfxj%nsxywzhy%~tzw%rjs" "please instruct your men" GHCi> decode 1 "up!qbsuz!ibse" "to party hard"
  28. 28. foldl使用時のスタックオーバーフロー • foldlはメモリの特定の領域を使い過ぎた時に起こる
 スタックオーバーフローエラーを引き起こすことがある GHCi> foldl (+) 0 (replicate 100 1) 100 GHCi> foldl (+) 0 (replicate 1000000 1) 1000000 -- H本だとここでstack overflow GHCi> foldl (+) 0 (replicate 10000000 1) 10000000 GHCi> foldl (+) 0 (replicate 100000000 1) -- 手元のマシンだとここで固まる(まさに外道)
  29. 29. stack overflowの理由 • Haskellは遅延評価であり実際の計算は
 可能な限り後まで引き伸ばされる • foldlを使う時、各ステップにおいて
 アキュムレータの計算を実際には行わない • 新しい計算で前の結果を参照するかもしれないので
 メモリ上に先延ばしにした計算を保持し続ける • メモリを使い果たしてスタックオーバーフロー
  30. 30. foldlの計算の様子 • 100万要素あると先延ばしにしていた計算が
 全て再帰的に行われるので
 スタックオーバーフローを引き起こす foldl (+) 0 [1,2,3] = foldl (+) (0 + 1) [2,3] = foldl (+) ((0 + 1) + 2) [3] = foldl (+) (((0 + 1 ) + 2) + 3) [] = ((0 + 1) + 2) + 3 = (1 + 2) + 3 = 3 + 3 = 6
  31. 31. 計算を先延ばしにしないfoldl • 左折り畳みの各ステップ間で計算が遅延されず
 すぐに評価されるfoldl —> 正格なfoldl foldl' (+) 0 [1,2,3] = foldl' (+) 1 [2,3] = foldl' (+) 3 [3] = foldl' (+) 6 [] = 6
  32. 32. Data.Listのfoldl’ • foldlの正格なバージョン • foldl1に足してもfoldl1’という正格なバージョンが存在 GHCi> :m + Data.List GHCi> foldl' (+) 0 (replicate 100000000 1) 100000000
  33. 33. • 通りを歩いていると老婦人が近付いて来て言いました • 「各桁の数の合計が40になる最初の自然数は何か」 • 123ならば1+2+3=6, では40になる数は? かっこいい数を見つけよう
  34. 34. 方針 • 数を引数として受け取り各桁の合計を求める関数を作る • showを使って数を文字列に変換 • Data.CharのdigitToIntを使って
 文字を数に変換 • これらを使って関数を書く • 各桁の合計が40になる最初の数を探す関数を作る • Data.Listのfind関数を使う GHCi> show 124 "124" GHCi> :m + Data.Char GHCi> digitToInt '2' 2 GHCi> digitToInt 'F' 15 GHCi> digitToInt 'z' *** Exception: 
 Char.digitToInt: not a digit 'z'
  35. 35. 各桁の合計を求める関数 import Data.Char import Data.List ! digitSum :: Int -> Int digitSum = sum . map digitToInt . show GHCi> :t show show :: Show a => a -> String GHCi> :t (map digitToInt) (map digitToInt) :: [Char] -> [Int] GHCi> :t sum sum :: Num a => [a] -> a GHCi> :t (sum . (map digitToInt) . show) (sum . (map digitToInt) . show) :: Show a => a -> Int
  36. 36. digitSumを適用した結果が
 40となる最初の数を探す関数 • Data.Listのfind関数を使う ! • 第一引数は述語、第二引数はリスト • Maybeとは? GHCi> :t find find :: (a -> Bool) -> [a] -> Maybe a
  37. 37. Maybe a型 • Maybe a型の値は0個か、ちょうど1個の要素だけを持てる • リスト型[a]が0個、1個、あるいはもっと沢山の要素を
 持てるのに似ている • 失敗する可能性があることの表現に使用
  38. 38. Maybe a型の値 • NothingかJust xのどちらか • Nothing • 0個の要素を持っている = 何も持っていない という値 • 空リストに似ている • Just x • Maybe a型のxを保持していることを表現
  39. 39. よく分からないので型を調べる GHCi> Nothing Nothing GHCi> :t Nothing Nothing :: Maybe a GHCi> Just "hey" Just "hey" GHCi> Just 3 Just 3 GHCi> :t Just "hey" Just "hey" :: Maybe [Char] GHCi> :t Just 3 Just 3 :: Num a => Maybe a GHCi> :t Just True Just True :: Maybe Bool
  40. 40. findの動作 • 述語を満たす要素が見つかったら、その要素をJustで ラップしたものが返される • 見つからなければNothingが返される GHCi> :t find find :: (a -> Bool) -> [a] -> Maybe a GHCi> find (> 4) [3,4,5,6,7] Just 5 GHCi> find odd [2,4,6,8,9] Just 9 GHCi> find (=='z') "mjolnir" Nothing
  41. 41. 各桁の合計が40になる
 最初の数を見つける関数 • 先程実装したdigitSum関数とfindを組み合わせる import Data.List import Data.Char ! digitSum :: Int -> Int digitSum = sum . map digitToInt . show ! firstTo40 :: Maybe Int firstTo40 = find (x -> digitSum x == 40) [1..] GHCi> firstTo40 Just 49999
  42. 42. 合計値をnとして一般化 import Data.List import Data.Char ! digitSum :: Int -> Int digitSum = sum . map digitToInt . show ! firstTo :: Int -> Maybe Int firstTo n = find (x -> digitSum x == n) [1..] GHCi> firstTo 27 Just 999 GHCi> firstTo 1 Just 1 GHCi> firstTo 13 Just 49
  43. 43. キーから値へのマッピング • 集合のようなデータを扱うときは順序を気にしない • 連想リストのキー • 例:ある住所に誰か住んでいるかを調べる • 住所で名前を検索したい • この節の話 • 望みの値(誰かの名前)を何らかのキー(その人の住所)で検索
  44. 44. 連想リストの表現 • 2つの方法が紹介 • ペア(タプル)のリストとして表現 • [(“betty”, “555-2938”), (“bonnie”, “452-2928”), …] • 線形に走査する必要有 • Data.Map • 高速な検索が可能
  45. 45. ペアのリストで表現 • 与えられたキーに対して値を検索する関数 findKey :: (Eq k) => k -> [(k, v)] -> v findKey key xs = snd . head . filter ((k, v) -> key == k) $ xs GHCi> let phoneBook = [("betty", "555-2938"), 
 ("bonnie", "452-2928"), ("patsy", "493-2928"), ("lucille", "205-2928"), ("wendy", "939-8282"), ("penny", "853-2492")] GHCi> findKey "betty" phoneBook "555-2938" GHCi> findKey "bety" phoneBook "*** Exception: Prelude.head: empty list
  46. 46. 検索関数の改良 • Maybe型を使う findKey :: (Eq k) => k -> [(k, v)] -> Maybe v findKey key [] = Nothing findKey key ((k,v):xs) | key == k = Just v | otherwise = findKey key xs • 畳み込みを用いた表現 findKey :: (Eq k) => k -> [(k, v)] -> Maybe v findKey key xs = foldr ((k, v) acc -> if key == k then Just v else acc) Nothing xs
  47. 47. 実行例 GHCi> findKey "betty" phoneBook Just "555-2938" GHCi> findKey "penny" phoneBook Just "853-2492" GHCi> findKey "betty" phoneBook Just "555-2938" GHCi> findKey "wilma" phoneBook Nothing • このfindKey関数はData.Listのlookup関数と同じ
  48. 48. Data.Map • ペアのリストで連想リストを表現するとキーに対応した値が見つかるまで すべての要素を走査する必要有 • Data.Mapモジュールに高速な連想リストが存在 • ここからは「連想リストを使う」と言う代わりに「Mapを使う」と表す
  49. 49. import • Data.MapはPreludeやData.Listと競合する名前をエクス ポートしているので修飾付きインポートする • import qualified Data.Map as Map • このimport文をスクリプトに書いて、
 それからそのスクリプトをGHCiでロード
  50. 50. Data.MapのfromList関数 • 連想リストを受け取り同じ対応関係を持つMapを返す • 連想リストのように表示されるがもはや連想リストではない GHCi> Map.fromList [("betty", "555-2938"), ("bonnie", "452-2928"), ("patsy", "493-2928"), ("lucille", "205-2928"), ("wendy", "939-8282"), ("penny", "853-2492")] Loading package array-0.5.0.0 ... linking ... done. Loading package deepseq-1.3.0.2 ... linking ... done. Loading package containers-0.5.5.1 ... linking ... done. fromList [("betty","555-2938"),("bonnie","452-2928"),("lucille","205-2928"), ("patsy","493-2928"),("penny","853-2492"),("wendy","939-8282")]
  51. 51. 特徴 • 重複したキーがあった場合、後のほうの要素が使われる GHCi> Map.fromList [("MS",1),("MS",2),("MS",3)] fromList [("MS",3)] • 型シグネチャ • キーの型クラスがOrdであり順序比較が必要 • 高速化の要因 GHCi> :t Map.fromList Map.fromList :: Ord k => [(k, a)] -> Map.Map k a
  52. 52. phoneBookのMapによる実装 import qualified Data.Map as Map ! phoneBook :: Map.Map String String phoneBook = Map.fromList $ [("betty" , "555-2938"), ("bonnie" , "452-2928"), ("patsy" , "493-2928"), ("lucille", "205-2928"), ("wendy" , "939-8282"), ("penny" , "853-2492")]
  53. 53. Map.lookupによる検索 GHCi> :t Map.lookup Map.lookup :: Ord k => k -> Map.Map k a -> Maybe a GHCi> Map.lookup "betty" phoneBook Just "555-2938" GHCi> Map.lookup "wendy" phoneBook Just "939-8282" GHCi> Map.lookup "grace" phoneBook Nothing
  54. 54. 新しい番号を挿入して新しいMapを作る GHCi> :t Map.insert Map.insert :: Ord k => k -> a -> Map.Map k a -> Map.Map k a GHCi> Map.lookup "grace" phoneBook Nothing GHCi> let newBook = Map.insert "grace" "341-9021" phoneBook GHCi> Map.lookup "grace" newBook Just "341-9021"
  55. 55. サイズを調べる GHCi> :t Map.size Map.size :: Map.Map k a -> Int GHCi> Map.size phoneBook 6 GHCi> Map.size newBook 7
  56. 56. 電話番号を文字列ではなくIntのリストとして表現 • “939-8282”ではなく[9,3,9,8,2,8,2]として表現したいが-が邪魔 • Data.CharのisDigitを使う GHCi> :t isDigit isDigit :: Char -> Bool GHCi> :t filter filter :: (a -> Bool) -> [a] -> [a] GHCi> :t digitToInt digitToInt :: Char -> Int import Data.Char ! string2digits :: String -> [Int] string2digits = map digitToInt . filter isDigit GHCi> :t (filter isDigit) (filter isDigit) :: [Char] -> [Char] GHCi> :t (map digitToInt) (map digitToInt) :: [Char] -> [Int] GHCi> :t (map digitToInt) . (filter isDigit) (map digitToInt) . (filter isDigit) :: [Char] -> [Int] GHCi> string2digits "948-9282" [9,4,8,9,2,8,2]
  57. 57. phoneBookをstring2digitsでマップ GHCi> let intBook = Map.map string2digits phoneBook GHCi> :t intBook intBook :: Map.Map String [Int] GHCi> Map.lookup "betty" intBook Just [5,5,5,2,9,3,8]
  58. 58. 電話帳の拡張 • キーの重複があるとfromListは重複を削除 • fromListWithを使う • 重複時の振る舞いを決められる
  59. 59. Map.fromListWith • 重複時に番号を結合 ! • 重複時にリストとして結合 ! • maxで最大値、(+)で値の和などを取ることも可能 phoneBookToMap :: (Ord k) => [(k, a)] -> Map.Map k [a] phoneBookToMap xs = Map.fromListWith add xs where add number1 number2 = number1 ++ ", " ++ number2 phoneBookToMap :: (Ord k) => [(k, a)] -> Map.Map k [a] phoneBookToMap xs = Map.fromListWith (++) $ map ((k, v) -> (k, [v])) xs
  60. 60. モジュールを作ってみよう • 再利用性を高めるためにモジュールに分ける • モジュールからは関数をエクスポートする • モジュールをインポートすると
 そのモジュールがエクスポートする関数が利用可能 • モジュール内部だけで使える関数も定義可能
  61. 61. 幾何学モジュール • Geometoryモジュールの作成 • Geometry.hsというファイル名にする • 先頭でモジュール名を指定 • module Geometry • その次にエクスポートする関数名を列挙 • (sphereVolume, sphereArea, …, cuboidVolume) where • その後で関数を定義
  62. 62. 階層的モジュール • Geometryを分割して立体の種類ごとに分割 • Geometryというディレクトリを作成 • その中に3つのファイルSphere.hs, Cuboid.hs, Cube.hsを作る • Geometryディレクトリのあるディレクトリ上のファイル からimport Geometry.Sphereなどとimport可能

×