2

这个问题确实更笼统,因为当我问它时,我发现了如何在这种特殊情况下解决它(即使我不喜欢它),但我会在我的特定上下文中使用它。

语境:

我正在使用镜头库,我发现为“添加”遍历提供功能特别有用(从概念上讲,遍历两个原始遍历中的所有元素的遍历)。我没有找到默认实现,所以我使用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...

我知道这与定义forall f. Functor f =>中的隐藏有关Traversal。在写这篇文章时,我意识到以下确实有效:

r_lens_idx :: Int -> ReifiedTraversal' [a] a
r_lens_idx idx = Traversal (lens_idx idx)

因此,通过给它参数,它可以使自己f显式,然后它可以使用它。但是,这感觉非常临时。特别是因为最初我试图在函数r_lens_idx定义的 where 子句中构建这个内联traversal_idxs(实际上......在定义这个内联函数的函数上,因为我真的不会经常使用它)。

所以,当然,我想我总是可以使用 lambda 抽象,但是......这真的是处理这个问题的正确方法吗?感觉就像是 hack,或者更确切地说,原始错误是类型检查器的疏忽。

4

2 回答 2

1

您想要的遍历的“添加”是在最近的镜头版本中添加的,您可以在名称adjoin下找到它。请注意,如果您的遍历完全重叠,则使用它是不合理的。

于 2020-02-12T04:45:04.820 回答
0

我正在回答我自己的问题,尽管它只是指出我试图用遍历做的事情实际上不可能以这种形式以及我如何克服它。仍然存在隐藏的 forall 量化变量的潜在问题,以及 lambda 抽象如何使不进行类型检查的代码突然进行类型检查(或者更确切地说,为什么它一开始就没有类型检查)。

事实证明,我的Monoidfor实现存在Traversal严重缺陷。当我开始调试它时,我意识到了。例如,我试图将一个索引列表和一个函数组合起来,该函数将为每个索引返回一个镜头,映射到列表中的那个索引,到一个将映射到这些索引的遍历。这是可能的,但它依赖于a的事实ListMonad,而不是仅仅使用Applicative结构。

我最初编写的函数add_traversal仅使用Applicative结构,但不是映射到列表中的那些索引,而是复制每个索引的列表,将它们连接起来,每个版本的列表都应用了它的镜头。

在尝试修复它时,我意识到我需要使用它bind来实现我真正想要的东西,然后我偶然发现了这个:https ://www.reddit.com/r/haskell/comments/4tfao3/monadic_traversals/

所以答案很明确:我可以做我想做的事,但这不是Monoidover Traversal,而是Monoidover 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它仍然是 aLensLike和 an ASetter,因此您可以使用 lens 包中的许多运算符,例如.~.

但是,正如我所提到的,由于 forall 量词处于一个不舒服的位置,我仍然必须在将其用于我的目的时使用 lambda 抽象,如果有人能澄清其中的类型检查器到底是怎么回事,我会很高兴看待。

于 2020-01-14T19:53:21.577 回答