すごいhaskell楽しく学ぼう 第6章
DESCRIPTION
スタートHaskell2で発表TRANSCRIPT
@aomoriringo
1986-2005
2005-2011
2011-
◎ Twitter: @aomoriringo
◎最近使ってる言語 ◦ C#
◎趣味 ◦ Mathematica
◎ Haskell歴 ◦ 2ヶ月未満
◎ いくつかの関数や型、型クラスなどを定義したファイル
◎ Haskellのプログラムはモジュールの集合
◎ コードをモジュールに分割するとしあわせ ◦ 再利用性を高める
◦ 管理・修正しやすくする
◎ モジュールをインポートする
◎ 標準モジュールを使ってみる ◦ 標準モジュールでいろいろ作ってみる
◦ foldl, foldl’
◦ 連想リスト
◎ 自分でモジュールを作ってみる
モジュールをインポートする
◎ 全てをインポート import Data.List
◎ nub, sortのみをインポート import Data.List (nub, sort)
◎ nub以外の全てをインポート import Data.List hiding (nub)
import qualified Data.Map
◦ Data.Map.filterのように参照
import qualified Data.Map as M
◦ M.filterのように参照
◎ module Fooにhogeとbarがあるとき
宣言 インポートされるもの 参照の方法
import Foo hoge, bar hoge, bar Foo.hoge, Foo.bar
import Foo () (なし)
import Foo (hoge) hoge hoge, Foo.hoge
import Foo hiding (hoge) bar bar, Foo.bar
import qualified Foo hoge, bar Foo.hoge, Foo.bar
import Foo as F hoge, bar hoge, bar F.hoge, F.bar
import qualified Foo as F hoge, bar F.hoge, F.bar
◎ Data.Listをインポート ghci> :m + Data.List
ghci> :m + Data.List Data.Map Data.Set
◎ Data.Listのインポートを解除 ghci> :m – Data.List
◎ importも使用可能 ghci> import Data.List (nub, sort)
ghci> import qualified Data.Map as M hiding (foldl)
標準モジュールを使ってみる
Data.List
Data.Char
Data.Map
◎ Hackage ◦ hackage.haskell.org/packages/hackage.html
◎ Hoogle ◦ http://www.haskell.org/hoogle/
◦ 標準モジュールのソースコードが見れます
◎ Hayoo! ◦ http://holumbus.fh-wedel.de/hayoo/hayoo.html
◎ 単語の個数を調べる
ghci> :t wordNums
wordNums :: String -> [(String,Int)]
ghci> wordNums "a haskell learn haskell a a learn good"
[("a",3),("good",1),("haskell",2),("learn",2)]
◎ 文字列を空白で区切られた文字列のリストに変換する
ghci> :t words
words :: String -> [String]
ghci> words “hey these are the words”
[“hey”, “these”, “are”, “the”, “words”]
ghci> words “hey these are the words”
[“hey”, “these”, “are”, “the”, “words”]
◎ リストの同じ要素をグループ化する
ghci> :t group
group :: Eq a => [a] -> [[a]]
ghci> group [1,1,1,2,2,2,3,3,2,2,5,7]
[[1,1,1],[2,2,2],[3,3],[2,2],[5],[7]]
ghci> group [“hoge”, “var”, “var”, “hoge”, “hoge”]
[[“hoge”], [“var”, “var”], [“hoge”, “hoge”]]
◎ リストを昇順に並べ替える
ghci> :t sort
sort :: Ord a => [a] -> [a]
ghci> sort [5,4,3,7,2,1]
[1,2,3,4,5,7]
ghci> sort [“hoge”, “var”, “var”, “hoge”, “hoge”]
[“hoge”, “hoge”, “hoge”, “var”, “var”]
文字列を単語に区切って (words)
→ソートして (sort)
→グループ化して (group)
→グループごとに数を数えれば
できそうですね!
◎ wordNumsの定義 wordNums :: String -> [(String,Int)]
wordNums = map (¥ws -> (head ws, length ws)) .
group . sort . words
ghci> wordNums “a b c b c”
[("a",1),(“b",2),(“c",2)]
ghci> wordNums “”
[]
◎ あるリストが別のリストに含まれるかどうかを調べる
ghci> :t isIn
isIn :: [a] -> [a] -> Bool
ghci> “art” `isIn` “party”
True
ghci> [1,2] `isIn` [1,3,5]
False
◎ リストに対してtailを繰り返し適用する
ghci> :t tails
tails :: [a] -> [[a]]
ghci> tails “party”
[“party”, “arty”, “rty”, “ty”, “y”, “”]
ghci> tails [1,2,3]
[[1,2,3], [2,3], [3], []]
◎ 2つ目のリストが1つ目のリストで 始まっているかどうかを返す
ghci> :t isPrefixOf
isPrefixOf :: String -> String -> Bool
ghci> “hawaii” `isPrefixOf` “hawaii joe”
True
ghci> “haha” `isPrefixOf` “ha”
False
ghci> “ha” `isPrefixOf` “haha”
True
“art” `isPrefixOf` “party” -- False
“art” `isPrefixOf` “arty” -- True
“art” `isPrefixOf` “rty” -- False
“art” `isPrefixOf` “ty” -- False
“art” `isPrefixOf` “y” -- False
◎ リストのうち、述語を満たすものがあるかどうかを返す
ghci> :t any
any :: (a -> Bool) -> [a] -> Bool
ghci> any (>4) [1,2,3]
False
ghci> any id [False, False, True]
True
◎ isInの定義 isIn :: (Eq a) => [a] -> [a] -> Bool
needle `isIn` haystack =
any (needle `isPrefixOf`) (tails haystack)
needle: 針。見つけたいリスト
haystack: 干し草の山。検索対象のリスト
◎ Data.List.isPrefixOf ◦ 前方一致
◦ さっき使った
◎ Data.List.isSuffixOf ◦ 後方一致
◎ Data.List.isInfixOf ◦ 中間一致
◦ さっき作った(isIn)
◎ シーザー暗号 ◦ 平文の各文字を辞書順にn文字シフトして作る
◎ 文字<->Unicode変換 ghci> :t ord
ord :: Char -> Int
ghci> ord ‘a’
97
ghci> :t chr
chr :: Int -> Char
ghci> chr 97
‘a’
ghci> map ord “abcdefgh”
[97,98,99,100,101,102,103,104]
◎ 指定した文字数分シフトする 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> encode 3 “hey mark”
“kh|#pdun”
ghci> decode 3 “kh|#pdun”
“hey mark”
ghci> decode 3 $ encode 3 “abc”
“abc”
◎ 各桁の数の合計がnになる最初の自然数をみつける
ghci> :t firstTo
firstTo :: Int -> Int
ghci> firstTo 1
1
ghci> firstTo 13
49
◎ 文字を数に変換する
ghci> :t digitToInt
digitToInt :: Char -> Int
ghci> digitToInt ‘2’
2
ghci> digitToInt ‘F’
15
ghci> digitToInt ‘z’
*** Exception: Char.digitToInt: not a digit 'z'
◎ 各桁の数の和を返す
digitSum :: Int -> Int
digitSum = sum . map digitToInt . show
ghci> digitSum 30
3
ghci> digitSum 1234
10
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’) “haskell”
Nothing
Nothing | Just a
◎ firstToの定義 firstTo :: Int -> Maybe Int
firstTo n =
find (¥x -> digitSum x == n) [1..]
ghci> firstTo 27
Just 999
ghci> firstTo 40
Just 49999
foldl, foldl’
ghci> foldl (+) 0 [1..2000000]
2000001000000 ◦ 遅い
◦ サイズ、環境によってはスタックオーバーフロー
ghci> Data.List.foldl’ (+) 0 [1..2000000]
2000001000000 ◦ foldlより速い
◦ オーバーフローしない
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
◎ 正格な(遅延でない)左畳み込み
foldl’ (+) 0 [1,2,3] =
foldl’ (+) 1 [2,3] =
foldl’ (+) 3 [3] =
foldl’ (+) 6 [] =
6
連想リスト
◎ 添字として任意の型を使用する配列
◎ 呼び方はいろいろ ◦ Map
◦ Dictionary
◦ Hash
◎ タプルを連想リストっぽく扱う ghci> :t lookup
lookup :: Eq a => a -> [(a,b)] -> Maybe b
ghci> lookup “b” [(“a”,1), (“b”,2), (“c”,3)]
Just 2
ghci> lookup “k” [(“a”,1), (“b”,2), (“c”,3)]
Nothing
◎連想リスト(Map)のモジュール ◦ Data.Map.lookup
◦ Data.Map.insert
◦ Data.Map.delete
◦ Data.Map.size
◦ etc..
◎ Data.Mapで連想リストを表す型
◎ keyとvalueの型を持つ
ghci> let m = Data.Map.fromList [(“foo”,1), (“bar”,2)]
ghci> :t m
m :: Data.Map.Map [Char] Integer
ghci> let m2 = Data.Map.fromList [(‘a’, True), (‘b’, False)]
ghci> :t m2
m2 :: Data.Map.Map Char Bool
ghci> import qualified Data.Map as Map
◎ keyで検索する ghci> let m = Map.fromList [(“a”,1), (“b”,2)]
ghci> Map.lookup “b” m
Just 2
ghci> Map.lookup “c” m
Nothing
◎ key=“c”, value=3の要素を挿入する ghci> let m2 = Map.insert “c” 3 m
ghci> Map.lookup “c” m2
Just 3
ghci> Map.size m2
3
◎ valueに関数を適用して新しいMapを作る
ghci> m2
fromList [("a",1),("b",2),("c",3)]
ghci> Map.map (^2) m2
fromList [("a",1),("b",4),("c",9)]
ghci> Map.map show m2
fromList [(“a”,”1”), (“b”,”2”), (“c”, “3”)]
◎ keyが重複した際の振る舞いを決める
ghci> Map.fromList [(1,2),(1,50),(1,10),(2,3)]
fromList [(1,10),(2,3)]
ghci> Map.fromListWith (+) [(1,2),(1,50),(1,10),(2,3)]
fromList [(1,62),(2,3)]
ghci> Map.fromListWith max [(1,2),(1,50),(1,10),(2,3)]
fromList [(1,50),(2,3)]
モジュールを作る
◎ 先頭にモジュール宣言を書く -- Foo.hs module Foo where ◦ モジュール名とファイル名は同じにしなければならない
◎ モジュールの中身を ◦ 全て公開したい(エクスポート) module Foo where
◦ 一部だけ公開したい
◦ module Foo (hoge, var) where
◎ test1/Nums.hs
module Nums where
double x = x + x
quadruple x = double x + double x
◎ test1/Main.hs
import Nums
double2 = Nums.double 2
quadruple4 = Nums.quadruple 4
◎ test1/Nums.hs
module Nums (double) where
double x = x + x
quadruple x = double x + double x
◎ test1/Main.hs
import Nums
double2 = Nums.double 2 -- OK
quadruple4 = Nums.quadruple 4 -- コンパイルエラー
◎ モジュールは階層構造を持つことが可能
◎ 階層構造は「A.B.C」のようにドット「.」で表す
◎ A.B.Cというモジュール名にした場合は、 A/B/C.hsというファイル構成にする
◎ test1/Figure/Sphere.hs
module Figure.Sphere where
Area radius = 4 * pi * (radius ^ 2)
◎ test1/Figure/Rectangle.hs
module Figure.Rectangle where
Area width height = width * height
◎ test1/Main.hs
import qualified Figure.Sphere as Sphere
import qualified Figure.Rectangle as Rect
sphereArea = Sphere.Area
rectArea = Rect.Area
◎ インポート, エクスポートはどちらも範囲を限定できる
◎ 標準モジュールを調べる, 使う, 理解するサイクルを 身につけたい
◎ 「こんなのないかな?」と思ったらHoogleとかで探す
◎ モジュールのエクスポートを気にしてきれいなコードを 書きたいですね