• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
 
 

 

on

  • 3,006 views

 

Statistics

Views

Total Views
3,006
Views on SlideShare
1,257
Embed Views
1,749

Actions

Likes
2
Downloads
32
Comments
0

5 Embeds 1,749

http://control.blog.fc2.com 1035
http://aomoriringo.hateblo.jp 512
http://makopi23.blog.fc2.com 198
http://webcache.googleusercontent.com 3
http://feedly.com 1

Accessibility

Categories

Upload Details

Uploaded via as Apple Keynote

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n

    Presentation Transcript

  • 「すごいHaskellたのしく学ぼう!」第7章型や型クラスを 自分で作ろう 第3回 スタートHaskell2 2012年8月19日 @a_hisame
  • 自己紹介• あひさめ(@a_hisame)• 社会人2年目 • 今のところはSIerさんのお仕事メイン• Haskell学習は3度目の正直• プログラミング、アナログゲーム全般、料理・お酒など
  • 7.1 データ型• 様々なデータ型• Bool, Int, Char, Maybe, ... etc• dataキーワードで新しいデータ型を定義-- Bool型を定義data Bool = False | True 新しいデータ型「Bool」を定義 このBool型はFalse、または、Trueの値を取り得る (これらは値コンストラクタでもある) データ型名、値コンストラクタは大文字で始まる必要がある
  • 7.2 形づくる-- フィールドの必要ないデータ型data Bool = False | True-- フィールドを持つデータ型data Shape = Circle Float Float Float | Rectangle Float Float Float Floatghci > :t RectangleRectangle :: Float -> Float -> Float -> Float -> Shape Circle, Rectangleは値コンストラクタ その後に渡す値を持つ、定義したデータ型の値を返す関数
  • 7.2 形づくる• 図形(Shape)の面積を求める関数を定義area :: Shape -> Floatarea ( Circle _ _ r ) = pi * r ^ 2area ( Rectangle x1 y1 x2 y2 ) = (abs $ x2-x1) * (abs $ y2 - y1) area関数はShape型を受け取り、その面積(Float)を返す関数 引数のShape型を受け取る部分でパターンマッチを行い 型ごとに適切な実装を行っている (この関数は任意のShape型の値を引数として受け取れる)
  • 7.2 形づくる• ghci上で次のコードを実行するdata Shape = Circle Float Float Float | Rectangle Float Float Float FloatCircle 10 20 5 <interactive>:19:1: No instance for (Show Shape) arising from a use of `print Possible fix: add an instance declaration for (Show Shape) In a stmt of an interactive GHCi command: print it Shapeというデータ型の値を文字列として表示する方法が分かりません (ghci上で実行した場合、その値の文字列表現を結果として表示する)
  • 7.2 形づくる• Shape型の定義に次の一文を追加data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show)Circle 10 20 5 deriving (Show) をつけることで、 Shapeデータ型の各値を文字列でどのように表現するかを 自動的に実装してくれる(詳しくは後述)
  • 7.2 形づくる• ところで・・・data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show)Circle 10 20 5 Circleの10, 20, 5というのは何を表している? Circle [中心のX座標] [中心のY座標] [円の半径] を想定 しているが、この順番を間違えてしまうかも。 Shapeというデータ型の値を文字列として表示する方法が分かりません (ghci上で実行した場合、その値の文字列表現を結果として表示する)
  • 7.2 形づくる• Point型を定義data Point = Point Float Float deriving (Show)data Shape = Circle Point Float | Rectangle Point Point deriving (Show)Circle (Point 10 20) 5 Circle [中心座標] [円の半径] ということを型で表現できる データ型を表現するために 新たなデータ型を定義して使用することができる
  • 7.2 形づくる• 図形(Shape)の面積を求める関数を修正area :: Shape -> Floatarea ( Circle _ r ) = pi * r ^ 2area ( Rectangle (Point x1 y1) (Point x2 y2) ) = (abs $ x2-x1) * (abs $ y2 - y1) area関数はShape型を受け取り、その面積(Float)を返す関数(修正版) データ型の中のデータ型もパターンマッチの対象にできる。
  • 7.2 形づくる• モジュールとしてエクスポートmodule Shapes ( Point(..), Shape(Circle, Rectangle), area, baseCircle) where データ型、値コンストラクタ(関数)、関数のエクスポート-- 値コンストラクタをエクスポートしない場合module Shapes (Point, Shape) where モジュールとしてデータ型、値コンストラクタを個別にエクスポートできる 値コンストラクタを提供せず、生成用の関数を提供することで 値コンストラクタを使ったパターンマッチはできなくなるが 既存のプログラムを壊さずに型の内部表現が変更可能
  • 7.3 レコード構文• 名前付きのフィールドを備えたデータ型の作成data Person = Person { firstName :: String, lastName :: String, age :: Int, height :: Float, phoneNumber :: String, flavor :: String } deriving (Show) フィールド名 :: フィールドの型 作成したデータ型のフィールドに それぞれ名前を持たせたデータ型を作成できる
  • 7.3 レコード構文• 値の作成と自動生成される関数-- フィールド名の関数(データ型 -> フィールド)を自動生成するghci> :t flavorflavor :: Person -> String-- レコード型の値を生成する(カンマ区切りでフィールドに値を設定)ghci> let p = Person { firstName = “Buddy”, lastName = “Finklestein”, age = 43, height = 184.2, phoneNumber = “526-2928”, flavor = “Chocolate” } レコード型のパラメータを名前付きで全て指定する(順序は自由)モジュールで値コンストラクタの指定をしなくてもレコード型の値を生成できる Q. レコード型の値をエクスポートするときにそれを公開しないようにできる?
  • 7.4 型引数• 値コンストラクタと型コンストラクタdata Point = Point Float Float deriving (Show)data Maybe a = Nothing | Just a このaが型引数ghci> Point 5 10 (型を与えることで使用する具体型が決定される)ghci> Point “x” “y” -- ERRORghci> Just 10 -- (Num a) => Maybe aghci> Just “string” -- Maybe [Char] 値コンストラクタ:値を受け取って新しい値(Ex. Point 5.0 10.0)を作る 型コンストラクタ:型を受け取って新しい型(Ex. Maybe Int)を作る (この例では単なるMaybeという型の値は存在できないが Maybe Intという型の値は存在できる)
  • 7.4 型引数• 様々な型を格納するデータ型が作れるdata Maybe a = Nothing | Just a-- もしも型引数がなければ...data IntMaybe = INothing | IJust Intdata StrMaybe = SNothing | SJust Stringghci> Just 10 :: Maybe Intghci> Just “string” :: Maybe String 異なるデータ型ごとにデータを作る必要がなくなる
  • 7.4 型引数• 多相的functionA :: Maybe Int -> StringfunctionB :: Maybe String -> Stringghci> :t [][] :: [a] -- ([] a)と意味は同じghci> :t (++)(++) :: [a] -> [a] -> [a] MaybeのNothingはfunctionA, functionBのどちらの引数としても扱える Nothingの型はMaybe aであるため (aの型の値を持たないので、aを決定する必要がない) 同様に空リストは任意の型引数を取るリストとして扱える
  • 7.4 型引数• 型引数の使いどころどんな型をそこに持ってきても変わらず動作するようなデータ構造Ex. Maybe, List, Map, Stack, Queue など• データ型宣言時のルールデータ型宣言には型クラス制約をつけない-- 順序型クラスの制約を持つ型を型引数に取るMaybedata (Ord a) => Maybe a = Nothing | Just a 不必要に型引数を使いすぎると返って煩雑にしすぎてしまう データに制約をつけず、関数の方に制約をつける (データにつけるとオプションが無い限りコンパイル時エラー)
  • 7.5 インスタンスの自動導出• 型クラスの自動導出データ型の宣言 deriving (自動導出する型クラス)• 自動導出可能な型クラス(P.28-32)Eq(等価判定), Ord(順序付け),Enum(列挙可能), Bounded(上限下限あり),Show(文字列に変換可能),Read(文字列から変換可能)
  • 7.5 インスタンスの自動導出• 自動導出全部入りdata Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday deriving (Eq, Ord, Show, Read, Bounded, Enum)ghci> Monday == Monday -- EqTrueghci> Friday < Sunday -- OrdTrue 書いた値で、最初に書いた値ほど小さい
  • 7.5 インスタンスの自動導出• 動作の確認ghci> show Monday -- Show“Monday”ghci> read “Monday” :: Day -- ReadTrueghci> [minBound .. maxBound] :: [Day][Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday] 最後の例はBoundedとEnumの複合 最小の要素(Bounded)と最大の要素(Bounded)を含む Day型の値を列挙していく(Enum)
  • 7.6 型シノニム• 既存データ型に別の名前を与えるtype String = [Char]type IntMaybe = Maybe Inttype Pair a b = (a, b)type MyStr = StringtestStr :: MyStr -> StringtestStr s = s コンパイルエラーghci> Pair 1 2ghci> testStr (“abc” :: [Char]) 別の名前を与えるだけの機能であり 値コンストラクタを作ってくれるわけではない 別名であるので、同じものを指すのであれば関数の引数に引き渡せる
  • 7.6 型シノニム• 型引数を2つ取る型の例data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)ghci> :t Left TrueLeft True :: Either True bghci> :t Right ‘a’Right ‘a’ :: Either a Char Leftは後の型引数bを多相のまま残して失敗を表し Rightは先の型引数aを多相のまま残して成功を表す Q. なぜLeftを失敗に使い、Rightを成功に使うのか?(Rightだから?)
  • 7.6 型シノニム• 失敗した時になぜ失敗したのかを返せるon1to10 :: Int -> Either Ordering Inton1to10 x | x < 1 = Left LT -- value less than [1,10] | x > 10 = Left GT -- value greater than [1,10] | otherwise = Right x -- value on [1,10] 整数が範囲内であるかどうかを判定する関数on1to10 範囲外である場合に、引数が小さいor大きいという 型情報Orderingの値を失敗値Leftと一緒に返す
  • 7.7 再帰的なデータ構造• フィールドに自分自身の型を持つデータ型data List’ a = Empty | Cons a (List’ a) deriving (Show, Read, Eq, Ord)ghci> EmptyEmptyghci> 4 `Cons` (5 `Cons` Empty)Cons 4 (Cons 5 Empty)ghci> :t ConsCons :: a -> List a -> List a List’はEmpty、もしくはCons値コンストラクタで作成された値 ConsコンストラクタはaとList’ aを取ってList’ aの値を作成する
  • 7.7 再帰的なデータ構造• イメージでつかもうdata List’ a = Empty | Cons a (List’ a) deriving (Show, Read, Eq, Ord) List’ Empty | Cons a (List’ a) 1 : 2 : 3 : []1 `Cons` 2 `Cons` 3 `Cons` Empty
  • 7.7 再帰的なデータ構造• 中置記号で書き直す コロン(:)から始まる記号のみの値コ ンストラクタは中置関数になるinfixr 5 :-:data List’ a = Empty | a :-: (List’ a) deriving (Show, Read, Eq, Ord)ghci> 1 :-: 2 :-: Emptyghci> (1 :-: ( 2 :-: Empty)) infixl or infixrで右結合か左結合かを指定する(省略すると右結合) また、結合性の優先順位を指定する(数字が大きいほど優先して結合) 省略した場合はinfixl 9が指定されていることと同じ 右結合を指定しない場合は下のように括弧をつける必要がある
  • 7.7 再帰的なデータ構造• リストを連結する関数を実装infixr 5 ^++(^++) :: List a -> List a -> List a(^++) Empty ys = ys(^++) (x :-: xs) ys = x :-: (xs ^++ ys) 記号だけで関数を実装すると、自動的に中置記法になる 関数の実装にパターンマッチを使用している パターンマッチとは値コンストラクタをマッチさせることに他ならない! (Emptyも:-:も値コンストラクタである) Q. 組み込みのリストでは[]を値コンストラクタとして使えるが このように記号を値コンストラクタにできる実装はある?
  • 7.7 再帰的なデータ構造• 木構造(2分探索木)data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show)Tree EmptyTree Node a (Tree a) (Tree b) Nodeは値aと左の木、右の木を持つ
  • 7.7 再帰的なデータ構造Tree EmptyTree Node a (Tree a) (Tree b) P.139に描かれている木の例 5 3 7 1 4 6 8
  • 7.7 再帰的なデータ構造• 木構造(2分[探索]木)data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show) この定義だけでは、ただの2分木を表現しているに過ぎない そのため、上記のデータ型では2分探索木では無いTreeも表現できる Q. 型引数に型クラス制約をつけないというルールはあったが、 データ構造上の制約をデータ型の定義の段階で入れることはあるのか?
  • 7.7 再帰的なデータ構造• 2分探索木を作成するための関数を定義singleton :: a -> Tree asingleton x = Node x EmptyTree EmptyTreetreeInsert :: (Ord a) => a -> Tree a -> Tree atreeInsert :: x EmptyTree = singleton xtreeInsert :: x (Node v left right) | x == v = Node v left right | x < v = Node v (treeInsert x left) right | x > v = Node v left (treeInsert x right) 木が空なら、新たなノードを作成する木に値がある場合、挿入する値が小さければ左へ、値が大きければ右へ挿入する
  • 7.7 再帰的なデータ構造• 2分探索木に特定の値があるかを探索する関数treeElem :: (Ord a) => a -> Tree a -> BooltreeElem :: x EmptyTree = FalsetreeElem :: x (Node v left right) | x == v = True | x < v = treeElem x left | x > v = treeElem x right 第2引数のTree aが2分探索木であれば正しい答えを返す Q. このような時にInvalidな値を渡された場合 Haskellではどのように対処するのか?
  • 7.7 再帰的なデータ構造• 2分探索木の作成let nums = [8,6,4,1,7,3,5]let tree = foldr treeInsert EmptyTree nums*Main> :t foldrfoldr :: (a -> b -> b) -> b -> [a] -> b foldrにはa,bの2匹数を受け取りbを返す関数(第1引数)と その関数に与える初期値(第2引数) および関数に順に与える値のリスト(第3引数)を渡す ここでは、まずtreeInsert 5 EmptyTreeが行われ その戻り値(Node 5 EmptyTree EmptyTree)に対して treeInsert 3 (Node 5 EmptyTree EmptyTree) が行われ ...と続いていき最終的にP.139の2分探索木を最終的に得る
  • 7.8 型クラス中級講座• WARNING!この先に進む前に型クラスという言葉に対して手続き型言語にでてくる「クラス」についてはとりあえず忘れて欲しいな! 型クラスは特定の振る舞いを表現するものであり、 JavaやPythonのクラスではなくインタフェースのようなもの 型クラスの振る舞いは、型クラス関数を定義することで得られる
  • 7.8 型クラス中級講座• Eq型クラスの実装 Eq型クラスの定義の始まり a: Eqのインスタンスになる型を表すclass Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool Eq型クラスのインスタンスであれば x == y = not (x /= y) 使用できる関数 ( == と /= ) x /= y = not (x == y) 型クラス関数のデフォルト実装 (省略可能) ある型をEq型クラスのインスタンスにしようとする場合 定義されている全ての関数を実装する必要がある Eq型クラスの場合はデフォルト実装が相互再帰しているので どちらか一方を書き換えるだけでよい
  • 7.8 型クラス中級講座• Eq型クラスのインスタンスの実装data TrafficLight = Red | Yellow | Greeninstance Eq TrafficLight where Red == Red = True Yellow == Yellow = True Green == Green = True _ == _ = False Eq型クラスの関数(==)の実装-- x /= y = not (x == y) (パターンマッチを利用) 関数(/=)は関数(==)を用いて定義されているので 関数(==)の実装を上書きすることでEq型クラスで 必要とされる関数の実装を行うことができる (型クラスの最小完全性定義)
  • 7.8 型クラス中級講座• 型クラスのサブクラスclass (Eq a) => Num a where ... 2つのインスタンス関係のイメージ Eq型クラスの インスタンス Num型クラスの インスタンス 型クラスNumのインスタンスとなる型は 型クラスEqのインスタンスである必要がある
  • 7.8 型クラス中級講座• 多層型(Maybe)のインスタンスinstance (Eq m) => Eq (Maybe m) where Just x == Just y = x == y Nothing == Nothing = True _ == _ = False Eq型クラスのインスタンスである型mを 持つ(Maybe m)の具体型に対して Eq型クラスのインスタンスを定義する (これはmに対して==を使用しているため) 逐一型ごとに(Maybe Int, Maybe Char, ...)インスタンスを実装せずとも 型を単に変数と記述してインスタンスを実装することが許されている
  • 7.8 型クラス中級講座• 型クラスについての情報ghci> :info Numclass Num a where (+) :: a -> a -> a (*) :: a -> a -> a (-) :: a -> a -> a negate :: a -> a abs :: a -> a signum :: a -> a fromInteger :: Integer -> a -- Defined in `GHC.Numinstance Num Integer -- Defined in `GHC.Numinstance Num Int -- Defined in `GHC.Numinstance Num Float -- Defined in `GHC.Floatinstance Num Double -- Defined in `GHC.Float :info [型クラス名]で、型クラスの性質を示す関数と 実装されているインスタンス一覧を表示することができる
  • 7.9 YesとNoの型クラス• 型クラスを自作するclass YesNo a where yesno :: a -> Bool• 自作の型クラスを独自実装する(例: Maybe)instance (YesNo m) => YesNo (Maybe m) where yesno (Just x) = yesno x yesno _ = False C言語やJavaScriptの条件判定がtrueとなる (ifの中に記述するとtrueと判断されるもの)を表現する型クラス 使用する時は型指定が必要なこともある (yesno 0だけではIntと推論してくれなかった)
  • 7.9 YesとNoの型クラス• If式(っぽいもの)を自作するyesnoIf :: (YesNo y) => y -> a -> a -> ayesnoIf yesnoVal tValue fValue | yesno yesnoVal = tValue | otherwise = fValue Haskellでは遅延評価であるため tValue, fValueの値が必要になるまで評価されない 一方で正格評価な言語では この関数が実行される段階でtValue, fValueともに 評価が終わっていなければならない
  • 7.10 Functor型クラス• 全体を写せる(map over)ものの型クラス class Functor something where fmap (a -> b) -> something a -> something b 1, 2, 3, ..., 26 型クラス 型クラス Int -> Char something Charsomething Int ‘A’, ‘B’, ‘C’, ..., ‘Z’ ある型がFunctorであるならば、その型に対するfmapが提供されている fと書くと単なる関数のように見えたのでsomethingで書き直してある
  • 7.10 Functor型クラス• Maybe, List, TreeなどはFunctor型クラスのインスタンス instance Functor Maybe where fmap f (Just x) = Just (f x) fmap f Nothing = Nothingclass Eq a where class Functor something where (==) :: a -> a -> Bool fmap (a -> b) -> (/=) :: a -> a -> Bool something a -> something b Functor型クラスのインスタンスとして指定する型は 型を1つ取る型コンストラクタを持つ必要がある (Functorは具体型では無く、型コンストラクタを要求している)
  • 7.10 Functor型クラス• 1つの型コンストラクタを持てば良いのでEitherでもOKinstance Functor (Either a) where fmap f (Right rx) = Right $ f rx fmap f (Left lx) = Left lx Either aのaは型変数 任意のaに対してのインスタ ンスを実装ghci> fmap (*2) (Right 10)Right 20ghci> fmap (*2) (Left “ABC”)Left “ABC” fmapの第1匹数はRightの値の型bを別の型に変換する関数 Leftに使われる型aに対しては写像関数fの対象ではない
  • 7.11 型を司るもの、種類• :kコマンドで型の種類を知ることができるghci> :k IntInt :: *ghci> :k MaybeMaybe :: * -> *ghci> :k Maybe IntMaybe Int :: *ghci> :k Either StringEither String :: * -> * *は具体型 (Intなど) * -> *は具体型を1つ取って具体型を返す型コンストラクタ (Maybeなど) * -> * -> *は具体型を2つ取って具体型を返す型コンストラクタ (Eitherなど) なお、部分適用を行うことも可能
  • 演習問題1• 木の最大の深さを求める関数を実装せよ。ただし、空の木 の深さは0とし、P.139の木の深さを3とするgetMaxDepth :: Tree a -> Int(以下を実装する)• 2分探索木の中から最大の値を取得する関数を実装せよgetMaxNode :: (Ord a) => Tree a -> Maybe a(以下を実装する)引数に渡されるTree aは正しい2分探索木であると仮定してよい
  • 演習問題2• 型クラスMyStackを次のように定義するclass MyStack s where push :: a -> s a -> s a pop :: s a -> Maybe (a, s a) peek :: s a -> Maybe aこの時、標準で提供されるリストをMyStackのインスタンスとして定義せよinstance MyStack [] where (以下を実装する)
  • 演習問題1: 解答例getMaxDepth :: Tree a -> IntgetMaxDepth EmptyTree = 0getMaxDepth (Node _ left right) = 1 + max (getMaxDepth left) (getMaxDepth right) 左と右の木の深い方の木に、1を加えたものが自分の深さgetMaxNode :: (Ord a) => Tree a -> Maybe agetMaxNode EmptyTree = NothinggetMaxNode (Node x _ EmptyTree) = Just xgetMaxNode (Node _ _ right) = getMaxNode right2分探索木なので、最も右側にあるノードが最大の値を保持する
  • 演習問題2: 解答例instance MyStack [] where push x xs = x:xs pop [] = Nothing pop (x:xs) = Just (x, xs) peek [] = Nothing peek (x:_) = Just x• push: 要素を先頭に追加したリストを返す• pop: 要素があれば、先頭から取り出した値と先頭を取り除 いたリストを返す• peek: 要素があれば、先頭の値を返す
  • 演習問題2: 動作例ghci> let stack = foldr push [] [1,2,3,4,5]ghci> stack[1,2,3,4,5]ghci> peek stackJust 1ghci> pop stackJust (1,[2,3,4,5])ghci> pop []Nothing