HaskellのIxedインスタンスを自作型につける
Ixed
とは
数学的解説はわかりませんごめんなさい。誰か補足があれば blogのレポジトリ にissueでも残してください()
Haskellなのでとりあえず hoogleを参照します。
Ixed
の定義は以下の通りです
class Ixed m where
-- |
-- /NB:/ Setting the value of this 'Traversal' will only set the value in
-- 'at' if it is already present.
--
-- If you want to be able to insert /missing/ values, you want 'at'.
--
-- >>> Seq.fromList [a,b,c,d] & ix 2 %~ f
-- fromList [a,b,f c,d]
--
-- >>> Seq.fromList [a,b,c,d] & ix 2 .~ e
-- fromList [a,b,e,d]
--
-- >>> Seq.fromList [a,b,c,d] ^? ix 2
-- Just c
--
-- >>> Seq.fromList [] ^? ix 2
-- Nothing
ix :: Index m -> Traversal' m (IxValue m)
default ix :: At m => Index m -> Traversal' m (IxValue m)
ix = ixAt
{-# INLINE ix #-}
Ixed
は Lens
の提供する型の一つで、 Map
のような型の値に対して
値を traverse
するシンプルな Traversal
を提供するものです。
簡潔に言うと、 リスト等の要素にLensでアクセスできるようにするやつ みたいなざっくりとした理解をしています。
また、これに関連するオープンな型ファミリーとして Index
と IxValue
があります
type family Index (s :: *) :: *
-- | This provides a common notion of a value at an index that is shared by both 'Ixed' and 'At'.
type family IxValue (m :: *) :: *
Ixed
において、 Index
はインデックスの型、 IxValue
はそこに格納されている
値の型です。
作る
とりあえず作り始めます。
前提として、今回~Ixed~のインスタンスを作るのは以下の型です。 元のファイルは Cj-bc/playground -- hit-n-blow で使われているものです。
-- | Represents each Pin
data Pin = Red | Blue | Green | White | Purple deriving (Show)
-- | One Set of Pins that user will guess
data Lane = Lane (Maybe Pin) (Maybe Pin) (Maybe Pin) (Maybe Pin) (Maybe Pin)
deriving (Show)
Ixed
の定義に特に制限がかかれていないので、 ix
を定義することにします。
そのために、 ix
で使用される Index
と IxValue
を定義することにします。
Index
Index
はあまり説明がありませんが、型の情報からすると恐らく「添字に使う型」
の定義であろうと推測が出来ます。
(名前が Index
であること、 ix
において最初に取ること等。又、
既にあるインスタンスを確認するのも良い方法だと思います。)
Lane
において添字は Int
です。
type instance Index Lane = Int
IxValue
同様ですが、今度はそれぞれの中身の型を定義します。
type instance IxValue Lane = Maybe Pin
Ixed
Ixed
本体に行きます!!
ix
の型は
ix :: Index m -> Traversal' m (IxValue m)
で、今回は m
が Lane
なので具体的な型にすると
ix :: Int -> Traversal' Lane (Maybe Pin)
ということになります。
で、 Lens
少ししか分からんので一つ疑問が浮かびます
>>>>> Traversal'
ってナニよ!!!!! <<<<<
Traversal' ってナニよ!
はい。名前は知ってるけど使い方良く分からずに放置してた子ですね。 定義によると
type Traversal' s a = Traversal s s a a
type Traversal s t a b = forall f. Applicative f => (a -> f b) -> s -> f t
ついでなので Traversal
の定義も載せておきました。
Lens
と同じように、実体はただの関数ですね。
Lens
よりも制限の緩い型で Traversable
の型関数である traverse
の一般化らしいです。
しっかりと理解はしていないが、まぁ型を考えれば作れてしまうのでとりあえずは
ふんわりと掴んだ状態で作ってみます。
あ、ちなみに Traversal'
は単純に、値の更新等した時に型が変化しないものですね。
参考:
ix
を作る
さて、 Traversal'
がわかったので ix
を作れ(る気がし)ます。
Traversal'
を置き換えてみると:
ix :: Int -> Traversa' Lane (Maybe Pin)
ix :: Int -> Traversal Lane Lane (Maybe Pin) (Maybe Pin)
ix :: Int -> (forall f. Applicative f => (Maybe Pin -> f (Maybe Pin) -> Lane -> f Lane
となります(forallの位置は少し自信がないけど多分あってる)
Int
は元々 Index m
だった部分なので、今興味のあるインデックス(に該当する数字)が来るのがわかります。
又、元の Traversal'
の部分も要は「中身( Maybe Pin
)に作用する関数を受け取り、作用させた
結果を返す」わけなので、その通りに実装します。
instance Ixed Lane where
ix 1 = \g l@(Lane a b c d e) -> Lane <$> g a <*> b <*> c <*> d <*> e
ix 2 = \g l@(Lane a b c d e) -> Lane a <$> g b <*> c <*> d <*> e
ix 3 = \g l@(Lane a b c d e) -> Lane a b <$> g c <*> d <*> e
ix 4 = \g l@(Lane a b c d e) -> Lane a b c <$> g d <*> e
ix 5 = \g l@(Lane a b c d e) -> Lane a b c d <$> g e
ix _ = \_ l -> pure l
多分動いた!!