这个问题确实更通用,因为当我问它时,我发现了如何在这种特殊情况下修复它(即使我不喜欢它(,但我会在我的特定上下文中表达它。
上下文:
我正在使用镜头库,我发现提供"添加"遍历的功能特别有用(从概念上讲,遍历两个原始遍历中的所有元素的遍历(。我没有找到默认实现,所以我使用Monoid
.为了能够实现实例,我必须使用ReifiedTraversal
包装器,我假设它在库中正是为此目的:
-- Adding traversals
add_traversals :: Semigroup t => Traversal s t a b -> Traversal s t a b -> Traversal s t a b
add_traversals t1 t2 f s = liftA2 (<>) (t1 f s) (t2 f s)
instance Semigroup t => Semigroup (ReifiedTraversal s t a b) where
a1 <> a2 = Traversal (add_traversals (runTraversal a1) (runTraversal a2))
instance Semigroup s => Monoid (ReifiedTraversal' s a) where
mempty = Traversal (_ -> pure . id)
我想从中提取的直接应用程序是能够为列表中的一组指定索引提供遍历。因此,底层半群是[]
的,底层Traversable
也是。首先,我为列表中的单个索引实现了一个镜头:
lens_idx :: Int -> Lens' [a] a
lens_idx _ f [] = error "No such index in the list"
lens_idx 0 f (x:xs) = fmap (rx -> rx:xs) (f x)
lens_idx n f (x:xs) = fmap (rxs -> x:rxs) (lens_idx (n-1) f xs)
剩下的就是将这两件事结合起来,理想情况下实现一个功能traversal_idxs :: [Int] -> Traversal' [a] a
问题:
当我尝试使用它时,我收到类型检查错误。我知道这与Traversal
是一种在其定义中包含约束forall
量词的类型有关。为了能够使用Monoid
实例,我需要首先具体化lens_idx
提供的镜头(当然,这也是遍历(。我尝试通过以下方式做到这一点:
r_lens_idx :: Int -> ReifiedTraversal' [a] a
r_lens_idx = Traversal . lens_idx
但这失败并出现两个错误(实际上是同一错误的两个版本(:
Couldn't match type ‘f’ with ‘f0’...
Ambiguous type variable ‘f0’ arising from a use of ‘lens_idx’
prevents the constraint ‘(Functor f0)’ from being solved...
我知道这与Traversal
定义中的隐藏forall f. Functor f =>
有关。在写这篇文章时,我意识到以下内容确实有效:
r_lens_idx :: Int -> ReifiedTraversal' [a] a
r_lens_idx idx = Traversal (lens_idx idx)
因此,通过给它参数,它可以使f
对自己明确,然后它可以使用它。但是,这感觉非常临时。特别是因为最初我试图在traversal_idxs
函数定义中的 where 子句中内联构建这个r_lens_idx
(实际上......在内联定义此函数的函数上,因为我不会经常使用它(。
所以,当然,我想我总是可以使用lambda抽象,但是...这真的是处理这个问题的正确方法吗?感觉就像一个黑客,或者更确切地说,原始错误是类型检查器的疏忽。
您想要的横移的"添加"是在最新的镜头版本中添加的,您可以在名称adnext下找到它。请注意,如果遍历完全重叠,则使用起来是不合理的。
我正在回答我自己的问题,尽管它只是指出我试图用遍历做的事情实际上在那种形状下是不可能的,以及我是如何克服它的。仍然存在隐藏的forall量化变量的潜在问题,以及lambda抽象如何可能使不进行类型检查的代码突然类型检查(或者更确切地说,为什么一开始没有类型检查(。
事实证明,我为Traversal
实现Monoid
存在严重缺陷。当我开始调试它时,我意识到了。例如,我试图组合一个索引列表和一个函数,该函数将为每个索引返回一个镜头,映射到列表中的该索引,再到将精确映射到这些索引的遍历。这是可能的,但它依赖于List
是一个Monad
的事实,而不仅仅是使用Applicative
结构。
我最初为add_traversal
编写的函数仅使用Applicative
结构,但它不会映射到列表中的那些索引,而是为每个索引复制列表,将它们连接起来,列表的每个版本都应用了它的镜头。
在尝试修复它时,我意识到我需要使用bind
来实现我真正想要的东西,然后我偶然发现了这个:https://www.reddit.com/r/haskell/comments/4tfao3/monadic_traversals/
所以答案很明确:我可以做我想做的事,但这不是Monoid
胜Traversal
,而是Monoid
胜MTraversal
。它仍然完美地服务于我的目的。
这是为此生成的代码:
-- Monadic traversals: Traversals that only work with monads, but they allow other things that rely on the fact they only need to work with monads, like sum.
type MTraversal s t a b = forall m. Monad m => (a -> m b) -> s -> m t
type MTraversal' s a = MTraversal s s a a
newtype ReifiedMTraversal s t a b = MTraversal {runMTraversal :: MTraversal s t a b}
type ReifiedMTraversal' s a = ReifiedMTraversal s s a a
-- Adding mtraversals
add_mtraversals :: Semigroup t => MTraversal r t a b -> MTraversal s r a b -> MTraversal s t a b
add_mtraversals t1 t2 f s = (t2 f s) >>= (t1 f)
instance Semigroup s => Semigroup (ReifiedMTraversal' s a) where
a1 <> a2 = MTraversal (add_mtraversals (runMTraversal a1) (runMTraversal a2))
instance Semigroup s => Monoid (ReifiedMTraversal' s a) where
mempty = MTraversal (_ -> return . id)
请注意,MTraversal
仍然是一个LensLike
和ASetter
,因此您可以使用镜头包中的许多运算符,例如.~
。
不过,正如我所提到的,由于 forall 量词处于一个不舒服的地方,我仍然必须使用它来达到我的目的,如果有人能澄清类型检查器在这方面到底是怎么回事,我会很高兴。