问题描述
最近几天,我一直在阅读附加语.当我从理论的角度开始理解它们的重要性时,我想知道人们在Haskell中如何以及为什么使用它们. Data.Functor.Adjunction
提供了一种实现,实例是免费仿函数/健忘仿函数和 curry/没事.从理论的角度来看,这些还是很有趣的,但是我看不到如何将它们用于更实际的编程问题.
I have been reading up on adjunctions during the last couple of days. While I start to understand their importance from a theoretical point of view, I wonder how and why people use them in Haskell. Data.Functor.Adjunction
provides an implementation and among its instances are free functor / forgetful functor and curry / uncurry. Again those are very interesting from the theoretical view point but I can't see how I would use them for more practical programming problems.
是否存在人们使用Data.Functor.Adjunction
解决的编程问题的示例,以及为什么您会比其他人更喜欢这种实现?
Are there examples of programming problems people solved using Data.Functor.Adjunction
and why you would prefer this implementation over others?
推荐答案
初步提示:这个答案有些投机.与问题非常相似,它是通过研究Data.Functor.Adjunction
.
我可以想到三个原因,说明为什么Adjunction
类的用例很少.
I can think of three reasons why there aren't many use cases for the Adjunction
class in the wild.
首先,所有Hask/Hask附件最终都在循环附件上有所变化,因此潜在实例的范围并不那么大.人们可能会感兴趣的许多附件不是Hask/Hask.
Firstly, all Hask/Hask adjunctions are ultimately some variation on the currying adjunction, so the spectrum of potential instances isn't all that large to begin with. Many of the adjunctions one might be interested on aren't Hask/Hask.
第二,虽然Adjunction
实例免费为您提供了大量其他实例,但在许多情况下,这些实例已经存在于其他地方.以ur为例,我们可以很容易地根据 Control.Monad.Trans.Adjoint
:
Secondly, while an Adjunction
instance gives you a frankly awesome amount of other instances for free, in many cases those instances already exist somewhere else. To pick the ur-example, we might very easily implement StateT
in terms of 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
实例,那么您可能会很幸运.我想到的一件事可能是有道理的(即使我实际上还没有真正看到它)是以下函子:
However, no one needs to actually do that, because there is a perfectly good StateT
in transformers. That said, if you do have an Adjunction
instance of your own, you might be in luck. One little thing I have thought of that might make sense (even if I haven't actually seen it out there) are the following functors:
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转换器.
We might write an Adjunction ChoiceF Dilemma
instance, which reflects how Dilemma (ChoiceF a)
is materialised version of State Bool a
. Dilemma (ChoiceF a)
can be thought of as a step in a decision tree: choosing one side of the Dilemma
tells you, through the ChoiceF
constructors, what choice is to be made next. The Adjunction
instance would then give us a monad transformer for Dilemma (ChoiceF a)
for free.
(另一种可能性可能是利用 Free f
/Cofree u
附加语.Cofree Dilemma a
是结果的无限树,而Free ChoiceF a
是实现结果的途径.
(Another possibility might be exploiting the Free f
/Cofree u
adjunction. Cofree Dilemma a
is an infinite tree of outcomes, while Free ChoiceF a
is a path leading to an outcome. I hazard there is some mileage to get out of that.)
第三,虽然Data.Functor.Adjunction
中有许多用于正确伴随的有用功能,但它们提供的大多数功能也可以通过Representable
和/或Distributive
使用,因此,大多数可能使用它们的地方最终都会粘住而是使用超类.
Thirdly, while there are many useful functions for right adjoints in Data.Functor.Adjunction
, most of the functionality they provide is also available through Representable
and/or Distributive
, so most places where they might be used end up sticking with the superclasses instead.
Data.Functor.Adjunction
还为 left 伴随提供了有用的功能.一方面,左连接(对成对同构,即容纳单个元素的容器)的通用性可能不如右连接(对函数同构,即具有单个形状的仿函数同构).另一方面,似乎没有用于左伴随的规范类(至少现在还没有),因此可能会导致实际使用Data.Functor.Adjunction
函数的机会.顺便说一句,您建议的克里斯·彭纳(Chris Penner)的战舰示例可以说是合适的,因为它确实依赖于左侧adjoint以及如何使用它来编码正确的adjoint表示形式:
Data.Functor.Adjunction
, of course, also offers useful functions for left adjoints. On the one hand, left adjoints (which are isomorphic to pairs i.e. containers that hold a single element) are probably less versatile than right adjoints (which are isomorphic to functions i.e. functors with a single shape); on the other hand, there doesn't seem to be any canonical class for left adjoints (not yet, at least), so that might lead to opportunities for actually using Data.Functor.Adjunction
functions. Incidentally, Chris Penner's battleship example you suggested arguably fits the bill, as it does rely on the left adjoint and how it can be used to encode the representation of the right adjoint:
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
使得(在这种情况下,字面意义上)在使用有效载荷的情况下定位位置成为可能.
CoordF
, the left adjoint, carries coordinates for the board and a payload. zapWithAdjunction
makes it possible to (quite literally, in this case), target the position while using the payload.
这篇关于Haskell中的附加用例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!