実例による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 なども手を入れる必要がある。


この記事が気に入ったらサポートをしてみませんか?