如何镜头到一个多态函数的记录字段上?
How to lens onto field of a record which is a polymorphic function?
我刚刚安装了 lens
库,因此我可以轻松地 set
嵌套数据结构。但是,我运行出了问题。这是一个演示我的问题的最小示例
以下代码无法编译:
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
data MyRecord = MyRecord
{ _func :: forall . a -> a
}
makeLenses ''MyRecord
changeMyRecord :: MyRecord -> MyRecord
changeMyRecord r = r & func .~ id
错误是No Instance for (Contravariant Identity) arising from use of 'func'
。
我看过 Contravariant
,我很确定我不可能创建这个实例,因为
class Contravariant f where
contramap :: (a -> b) -> f b -> f a
即如果 f = \x -> x
我看不到我要在哪里找到 a
类型的东西以应用于函数参数 (a-> b)
有没有其他方法可以使用镜头修改 MyRecord
?或者我是否可以以某种方式避免 RankNTypes
,但仍然在我的记录中传递多态 _func
?或者别的什么?
记录更新语法不可行 - 假设 MyRecord
是深度嵌套的。
回答时请假设知识很少haskell,特别是我今天才开始看镜头库
lens
在这里搞砸了——不可能将 func
用作类型为
的镜头(或其他可写入的光学元件)
func :: Lens' MyRecord (a -> a)
因为这意味着您可以放入任何具体类型的内函数,例如
changeMyRecord :: MyRecord -> MyRecord
changeMyRecord r = r & func .~ ((+1) :: Int -> Int)
所以相反,它使 func
只有一个 getter
func :: Getter' MyRecord (a -> a)
...没关系,因为通用多态函数 可以 用于任何类型,因此以下工作:
useMyRecord :: MyRecord -> String
useMyRecord r = show (r^.func $ 1 :: Int)
看到那个
type Getter s a = ∀ f. (Contravariant f, Functor f) => (a -> f a) -> s -> f s
这就是 Contravariant
约束的来源。 No Instance for Contravariant
错误消息只是 Can't use a ‘Getter’ as a ‘Setter’
.
的 VanLaarhoven-Kmett-ish
你真正想要的当然是
func :: Lens' MyRecord (∀ a . a -> a)
但不幸的是 impredicative type, which Haskell doesn't support。也就是说,它将扩展到
func :: ∀ f . Functor f => ((∀ a . a -> a) -> f (∀ a . a -> a)) -> MyRecord -> f MyRecord
请注意 f
中有一个 ∀
。
要获得这种多态场透镜的语义,您需要将其包装在 Rank-0 类型中:
newtype PolyEndo = PolyEndo { getPolyEndo :: ∀ a . a -> a }
data MyRecord = MyRecord
{ _func :: PolyEndo
}
makeLenses ''MyRecord
-- func :: Lens' MyRecord PolyEndo
我刚刚安装了 lens
库,因此我可以轻松地 set
嵌套数据结构。但是,我运行出了问题。这是一个演示我的问题的最小示例
以下代码无法编译:
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
data MyRecord = MyRecord
{ _func :: forall . a -> a
}
makeLenses ''MyRecord
changeMyRecord :: MyRecord -> MyRecord
changeMyRecord r = r & func .~ id
错误是No Instance for (Contravariant Identity) arising from use of 'func'
。
我看过 Contravariant
,我很确定我不可能创建这个实例,因为
class Contravariant f where
contramap :: (a -> b) -> f b -> f a
即如果 f = \x -> x
我看不到我要在哪里找到 a
类型的东西以应用于函数参数 (a-> b)
有没有其他方法可以使用镜头修改 MyRecord
?或者我是否可以以某种方式避免 RankNTypes
,但仍然在我的记录中传递多态 _func
?或者别的什么?
记录更新语法不可行 - 假设 MyRecord
是深度嵌套的。
回答时请假设知识很少haskell,特别是我今天才开始看镜头库
lens
在这里搞砸了——不可能将 func
用作类型为
func :: Lens' MyRecord (a -> a)
因为这意味着您可以放入任何具体类型的内函数,例如
changeMyRecord :: MyRecord -> MyRecord
changeMyRecord r = r & func .~ ((+1) :: Int -> Int)
所以相反,它使 func
只有一个 getter
func :: Getter' MyRecord (a -> a)
...没关系,因为通用多态函数 可以 用于任何类型,因此以下工作:
useMyRecord :: MyRecord -> String
useMyRecord r = show (r^.func $ 1 :: Int)
看到那个
type Getter s a = ∀ f. (Contravariant f, Functor f) => (a -> f a) -> s -> f s
这就是 Contravariant
约束的来源。 No Instance for Contravariant
错误消息只是 Can't use a ‘Getter’ as a ‘Setter’
.
你真正想要的当然是
func :: Lens' MyRecord (∀ a . a -> a)
但不幸的是 impredicative type, which Haskell doesn't support。也就是说,它将扩展到
func :: ∀ f . Functor f => ((∀ a . a -> a) -> f (∀ a . a -> a)) -> MyRecord -> f MyRecord
请注意 f
中有一个 ∀
。
要获得这种多态场透镜的语义,您需要将其包装在 Rank-0 类型中:
newtype PolyEndo = PolyEndo { getPolyEndo :: ∀ a . a -> a }
data MyRecord = MyRecord
{ _func :: PolyEndo
}
makeLenses ''MyRecord
-- func :: Lens' MyRecord PolyEndo