在过去的几天里,我一直在阅读adjunctions。当我从理论的角度开始理解它们的重要性时,我想知道人们在Haskell中如何以及为什么使用它们。 Data.Functor.Adjunction 提供了一个实现,其实例包括free functor / forgetful functor和curry / uncurry。从理论的角度来看,这些再次很有趣,但是我看不到如何将它们用于更实际的编程问题。

是否存在人们使用Data.Functor.Adjunction解决的编程问题的示例,以及为什么您更喜欢这种实现?

最佳答案

初步说明:这个答案有些投机。与问题非常相似,它是通过研究Data.Functor.Adjunction构建的。

我可以想到三个原因,为什么在野外没有很多Adjunction类的用例。

首先,所有Hask / Hask附件最终都在循环附件上有所变化,因此潜在实例的范围并不是那么大。一个可能感兴趣的许多附件不是Hask / Hask。

其次,虽然Adjunction实例免费为您提供了大量的其他实例,但在许多情况下,这些实例已经存在于其他地方。以ur-example为例,我们可以很容易地根据 StateT 实现Control.Monad.Trans.Adjoint:

newtype StateT s m a = StateT { runStateT :: s -> m (s, a) }
  deriving (Functor, Applicative, Monad) via AdjointT ((,) s) ((->) s) m
  deriving MonadTrans via AdjointT ((,) s) ((->) s)
  -- There is also a straightforward, fairly general way to implement MonadState.

但是,实际上没有人需要这样做,因为在转换器中有一个非常好的StateT。就是说,如果您确实拥有自己的Adjunction实例,那么您可能会很幸运。我想到的一件事可能是有道理的(即使我实际上还没有真正看到过)以下函子:
data Dilemma a = Dilemma { fstDil :: a, sndDil a }

data ChoiceF a = Fst a | Snd a

我们可以编写一个Adjunction ChoiceF Dilemma实例,该实例反映Dilemma (ChoiceF a)State Bool a的物化版本。可以将Dilemma (ChoiceF a)视为决策树中的一个步骤:选择Dilemma的一侧将通过ChoiceF构造函数告诉您下一步要进行的选择。然后,Adjunction实例将免费为我们提供Dilemma (ChoiceF a)的monad转换器。

(另一种可能性可能是利用the Free f / Cofree u adjunctionCofree Dilemma a是结果的无限树,而Free ChoiceF a是导致结果的路径。我冒昧地冒险。

第三,尽管Data.Functor.Adjunction中有许多有用的用于正确伴随的功能,但它们提供的大多数功能也可以通过Representable和/或Distributive获得,因此,大多数可能使用它们的地方最终都坚持使用 super class 。
Data.Functor.Adjunction当然也为左伴随提供有用的功能。一方面,左连接(与线对同构,即容纳单个元素的容器)的通用性可能不如右连接(与功能,即具有单一形状的函子同构)。另一方面,似乎没有用于左伴随的规范类(至少现在还没有),因此可能导致实际使用Data.Functor.Adjunction函数的机会。顺便提一句,您建议的Chris Penner's battleship example可以说是合适的,因为它确实依赖于左伴随项以及如何使用它来编码右伴随项的表示形式:
zapWithAdjunction :: Adjunction f u => (a -> b -> c) -> u a -> f b -> c
zapWithAdjunction @CoordF @Board :: (a -> b -> c) -> Board a -> CoordF b -> c

checkHit :: Vessel -> Weapon -> Bool

shoot :: Board Vessel -> CoordF Weapon -> Bool
CoordF(左侧的伴随物)承载着木板和有效载荷的坐标。 zapWithAdjunction可以(在这种情况下,字面意义上)在使用有效负载时定位位置。

08-24 15:49