我一直在尝试编写自定义的Optics数据结构,以概括镜头,棱镜和遍历。我的数据结构如下:
data Optic m a b = Optic { view :: a -> m b
, over :: a -> (b -> b) -> a
}
我想编写一个包含两个光学元件optic1 :: Optic m a b
和optic2 :: Optic n b c
的函数,以生成包含view :: a -> m (n c)
和over :: a -> (c -> c) -> a
的光学元件。
在我的脑海中,这种组成的光学元件的类型为Optic (m n) a c
,但这是行不通的-GHC会抱怨m的类型参数太多,而n的类型参数太少。
这是我对compose函数的非编译实现:
compose :: Optic m a b -> Optic n b c -> (m b -> (b -> n c) -> m (n c)) -> Optic (m n) a c
compose optic1 optic2 glue = Optic { view = viewCompose (view optic1) (view optic2) glue
, over = overCompose (over optic1) (over optic2)
}
viewCompose :: (a -> m b) -> (b -> n c) -> (m b -> (b -> n c) -> m (n c)) -> a -> m (n c)
viewCompose view1 view2 glue x = glue (view1 x) view2
overCompose :: (a -> (b -> b) -> a) -> (b -> (c -> c) -> b) -> a -> (c -> c) -> a
overCompose over1 over2 x f = over1 x (\y -> over2 y f)
GHC错误消息是:
optic.hs:7:83: error:
• Expecting one fewer argument to ‘m n’
Expected kind ‘* -> *’, but ‘m n’ has kind ‘*’
• In the first argument of ‘Optic’, namely ‘m n’
In the type signature:
compose :: Optic m a b
-> Optic n b c -> (m b -> (b -> n c) -> m (n c)) -> Optic (m n) a c
optic.hs:7:85: error:
• Expecting one more argument to ‘n’
Expected a type, but ‘n’ has kind ‘* -> *’
• In the first argument of ‘m’, namely ‘n’
In the first argument of ‘Optic’, namely ‘m n’
In the type signature:
compose :: Optic m a b
-> Optic n b c -> (m b -> (b -> n c) -> m (n c)) -> Optic (m n) a c
如果我创建类型为Optic Maybe Int Int
的光学元件,GHC会理解第一个类型参数的类型为* -> *
,并且不会抱怨参数不足。但是我不知道如何将类型组合在一起以创建另一种类型的* -> *
。
是否有任何方式(带有或不带有语言扩展名)来表达以下内容:
Optic (forall t. m (n t)) a c
答案 0 :(得分:1)
根据@chi的评论,Haskell不直接支持类型级lambda。因此,尽管存在类型为Maybe
的类型为* -> *
的类型,它直接表示类型级别的lambda \a ~> Maybe a
,但是没有直接表示类型级别的lambda \a ~> Maybe (Maybe a)
的对应类型
这意味着给定您为字段view
定义的类型:
view :: a -> m b
不可能找到满足以下条件的任何类型的Optic m a b
光学元件m
:
view :: a -> Maybe (Maybe b) -- impossible
您必须为这些类型使用 some 类型的编码。从Compose
导入的Data.Functor.Compose
新类型是一种选择。它的定义是:
newtype Compose m n a = Compose (m (n a))
它基本上将没有直接Haskell表示形式的lambda \a ~> m (n a)
类型包装为直接具有Haskell表示形式\a ~> (Compose m n) a
的lambda类型Compose m n : * -> *
。
缺点是它将在您的类型中引入不均匀性-将有Optic Maybe Int Int
之类的“普通”光学元件,然后是Optic (Compose Maybe Maybe) Int Int
之类的“组合”光学元件。在大多数情况下,您可以使用coerce
来解决这种不便。
使用compose
新类型对Compose
进行适当的定义应类似于:
type Glue m n b c = m b -> (b -> n c) -> m (n c)
compose :: Optic m a b -> Optic n b c -> Glue m n b c -> Optic (Compose m n) a c
compose optic1 optic2 glue
= Optic { view = viewCompose (view optic1) (view optic2) glue
, over = overCompose (over optic1) (over optic2)
}
where
viewCompose view1 view2 glue x = Compose $ glue (view1 x) view2
overCompose over1 over2 x f = over1 x (\y -> over2 y f)
和典型的基于Maybe
的光学元件:
_Left :: Optic Maybe (Either a b) a
_Left = Optic v o
where v (Left x) = Just x
v (Right _) = Nothing
o (Left x) f = Left (f x)
o (Right y) _ = Right y
一个合成的光学元件可能看起来像:
_Left2 = compose _Left _Left (flip fmap)
直接使用它会引入一个Compose
包装器:
> view _Left2 (Left (Left "xxx"))
Compose (Just (Just "xxx"))
但是您可以coerce
的结果来避免显式展开,如果有多个嵌套的Compose
层,则特别有用:
λ> import Data.Coerce
λ> _Left4 = compose _Left2 _Left2 (flip fmap)
λ> :t _Left4
_Left4
:: Optic
(Compose (Compose Maybe Maybe) (Compose Maybe Maybe))
(Either (Either (Either (Either c b4) b5) b6) b7)
c
λ> view _Left4 (Left (Left (Left (Left True))))
Compose (Compose (Just (Just (Compose (Just (Just True))))))
λ> coerce $ view _Left4 (Left (Left (Left (Left True)))) :: Maybe (Maybe (Maybe (Maybe Bool)))
Just (Just (Just (Just True)))
完整代码:
import Data.Coerce
import Data.Functor.Compose
data Optic m a b = Optic { view :: a -> m b
, over :: a -> (b -> b) -> a
}
type Glue m n b c = m b -> (b -> n c) -> m (n c)
compose :: Optic m a b -> Optic n b c -> Glue m n b c -> Optic (Compose m n) a c
compose optic1 optic2 glue
= Optic { view = viewCompose (view optic1) (view optic2) glue
, over = overCompose (over optic1) (over optic2)
}
where
viewCompose view1 view2 glue x = Compose $ glue (view1 x) view2
overCompose over1 over2 x f = over1 x (\y -> over2 y f)
_Left :: Optic Maybe (Either a b) a
_Left = Optic v o
where v (Left x) = Just x
v (Right _) = Nothing
o (Left x) f = Left (f x)
o (Right y) _ = Right y
_Left2 :: Optic (Compose Maybe Maybe) (Either (Either c b1) b2) c
_Left2 = compose _Left _Left (flip fmap)
_Left4 :: Optic (Compose (Compose Maybe Maybe) (Compose Maybe Maybe)) (Either (Either (Either (Either c b1) b2) b3) b4) c
_Left4 = compose _Left2 _Left2 (flip fmap)
main = do
print $ view _Left4 (Left (Left (Left (Left True))))
print $ (coerce $ view _Left4 (Left (Left (Left (Left True)))) :: Maybe (Maybe (Maybe (Maybe Bool))))