実例によるPureScript 5 章
パターン照合の章である。使いこなせれば強力なのは理解できる。記法が慣れないのは書いて覚えるしかないか。
演習 1.(簡単)パターン照合を使用して、階乗関数を書いてみましょう。ヒント:入力がゼロのときとゼロでないときの、ふたつの場合を考えてみてください。
factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)
演習 2. (やや難しい)二項係数を計算するためのパスカルの公式(Pascal's Rule、パスカルの三角形を参照のこと)について調べてみてください。パスカルの公式を利用し、パターン照合を使って二項係数を計算する関数を記述してください。
pascalsRule :: Int -> Int -> Int
pascalsRule 0 _ = 1
pascalsRule _ 0 = 1
pascalsRule n r | n == r = 1
| otherwise = pascalsRule (n - 1) (r - 1) + pascalsRule (n - 1) r
$${_0C_r (r > 0)}$$ で 1 を返すのはいいのか…
演習 1. (簡単)レコードパターンを使って、2つの Personレコードが同じ都市にいるか探す関数 sameCityを定義してみましょう。
sameCity :: Person -> Person -> Boolean
sameCity { address: { city: c1 } } { address: { city: c2 } } = c1 == c2
-- sameCity _ _ = false この行は不要
演習 2. (やや難しい)行多相を考慮すると、 sameCity関数の最も一般的な型は何でしょうか?先ほど定義した livesInLA関数についてはどうでしょうか?
なんと答えたらいいのかちょっとよくわからんのでとりあえず型を調べた。`showPerson` もだが、PSCi の出力がちょっと違う。なんとなく雰囲気的に任意となっている箇所はわかる。
> :t showPerson
forall (t14 :: Row Type).
{ first :: String
, last :: String
| t14
}
-> String
> :t livesInLA
forall (t45 :: Row Type) (t48 :: Row Type).
{ address :: { city :: String
| t48
}
| t45
}
-> Boolean
> :t sameCity
forall (t24 :: Row Type) (t27 :: Row Type) (t30 :: Row Type) (t33 :: Row Type) (a35 :: Type).
Eq a35 => { address :: { city :: a35
| t27
}
| t24
}
-> { address :: { city :: a35
| t33
}
| t30
}
-> Boolean
演習 3. (やや難しい)配列リテラルパターンを使って、1要素の配列の唯一のメンバーを抽出する関数fromSingletonを書いてみましょう。1要素だけを持つ配列でない場合、関数は指定されたデフォルト値を返さなければなりません。この関数は forall a. a -> Array a -> a.という型を持っていなければなりません。
fromSingleton :: forall a. a -> Array a -> a
fromSingleton _ [s] = s
fromSingleton default _ = default
演習 1.(簡単)半径 10で中心が原点にある円を表す Shapeの値を構築してください。
exampleCircle :: Shape
exampleCircle = Circle { x: 0.0, y: 0.0 } 10.0
演習 2. (やや難しい)引数の Shapeを原点を中心として 2.0倍に拡大する、 Shapeから Shapeへの関数を書いてみましょう。
中心も原点から 2 倍に遠ざけるようにした。
scaleByTwo :: Shape -> Shape
scaleByTwo (Circle { x, y } r) = Circle { x: x * 2.0, y: y * 2.0 } (r * 2.0)
scaleByTwo (Rectangle { x, y } w h) = Rectangle { x: x * 2.0, y: y * 2.0 } (w * 2.0) (h * 2.0)
scaleByTwo (Line { x: x1, y: y1 } { x: x2, y: y2 }) = Line { x: x1 * 2.0, y: y1 * 2.0 } { x: x2 * 2.0, y: y2 * 2.0 }
scaleByTwo (Text { x, y } s) = Text { x: x * 2.0, y: y * 2.0 } s
-- scaleByTwo (Clipped s p) = Clipped (scaleByTwo s) (map scaleByTwo p)
演習 3. (やや難しい) Shapeからテキストを抽出する関数を書いてください。この関数は Maybe Stringを返さなければならず、もし入力が Textを使用して構築されたのでなければ、返り値には Nothing構築子を使ってください。
getString :: Shape -> Maybe String
getString (Text _ s) = Just s
getString _ = Nothing
演習 1. (やや難しい) ベクターグラフィックライブラリを拡張し、 Shapeの面積を計算する新しい操作 areaを追加してください。この演習では、テキストの面積は0であるものとしてください。
area :: Shape -> Number
area (Circle _ r) = Number.pi * r * r
area (Rectangle _ w h) = w * h
area (Line _ _) = 0.0
area (Text _ _) = 0.0
-- area (Clipped _ _) = 0.0 -- Not implemented.
演習 2. (難しい) Shapeを拡張し、新しいデータ構築子 Clippedを追加してください。 Clippedは他の Pictureを矩形に切り抜き出ます。切り抜かれた Pictureの境界を計算できるよう、 shapeBounds関数を拡張してください。これは Shapeを再帰的なデータ型にすることに注意してください。
data Shape
= Circle Point Number
| Rectangle Point Number Number
| Line Point Point
| Text Point String
| Clipped Shape Picture
と拡張し、
shapeBounds (Clipped shape pict) = clipped
where
pictBounds = bounds pict
sBounds = shapeBounds shape
clipped = intersect sBounds pictBounds
とした。ビルドするには showShape や getCenter なども手を入れる必要がある。
この記事が気に入ったらサポートをしてみませんか?