不是函子/函数/应用/单子的好例子?


209

在向某人解释什么是类型类X时,我努力地找到了恰好是X的数据结构的良好示例。

因此,我要求提供以下示例:

  • 不是Functor的类型构造函数。
  • 类型构造函数,是Functor,但不是Applicative。
  • 类型构造器,它是一个Applicative,但不是Monad。
  • 类型构造器,它是Monad。

我认为到处都有很多Monad的例子,但是与以前的例子有一些联系的Monad很好的例子可以使情况更完整。

我正在寻找彼此相似的示例,仅在对于属于特定类型类的重要方面有所不同。

如果能够在这个层次结构中的某个地方(在Applicative和Monad之间找到一个)Arrow的示例,那也很棒!


4
是否有可能使类型构造器(* -> *没有合适的类型fmap
欧文(Owen)

1
欧文,我认为a -> String这不是函子。
Rotsor

3
@Rotsor @Owen a -> String是数学函子,但不是Haskell Functor
J. Abrahamson 2014年

@J。亚伯拉罕森(Abrahamson),从什么意义上讲,它是数学函子?您是否在谈论带有反向箭头的类别?
Rotsor

3
对于人们不知道的,一个逆变函子具有fmap类型(a -> b) -> f b -> f a
AJFarmar 2015年

Answers:


100

不是Functor的类型构造函数:

newtype T a = T (a -> Int)

您可以使用它生成逆变函子,但不能使用(协变)函子。尝试写作fmap,你会失败。请注意,逆函子版本是相反的:

fmap      :: Functor f       => (a -> b) -> f a -> f b
contramap :: Contravariant f => (a -> b) -> f b -> f a

类型构造函数,它是一个函子,但不适用:

我没有一个很好的例子。有Const,但理想情况下,我想要一个具体的非Monoid,我想不出任何东西。基本上,所有类型都是数字,枚举,乘积,总和或函数。您可以在下面看猪工,我对于是否Data.Void是一个工人意见不一Monoid;

instance Monoid Data.Void where
    mempty = undefined
    mappend _ _ = undefined
    mconcat _ = undefined

因为_|_在Haskell中是合法值,并且实际上是的唯一合法值Data.Void,所以这符合Monoid规则。我不确定unsafeCoerce与它有什么关系,因为不再保证您的程序在使用任何unsafe函数后就不会违反Haskell语义。

请参阅Haskell Wiki,以获取底部的文章(link)或不安全的函数(link)。

我想知道是否有可能使用更丰富的类型系统(例如Agda或具有各种扩展名的Haskell)创建这种类型构造函数。

类型构造器,它是可应用的,但不是Monad:

newtype T a = T {multidimensional array of a}

您可以使用以下方法制作一个应用程序:

mkarray [(+10), (+100), id] <*> mkarray [1, 2]
  == mkarray [[11, 101, 1], [12, 102, 2]]

但是,如果将其设置为monad,则可能会出现尺寸不匹配的情况。我怀疑这样的例子在实践中很少见。

一个Monad类型构造函数:

[]

关于箭:

问箭头在此层次结构上的位置就像问“红色”是哪种形状。注意种类不匹配:

Functor :: * -> *
Applicative :: * -> *
Monad :: * -> *

但,

Arrow :: * -> * -> *

3
好名单!我建议使用更简单Either a的示例作为最后一种情况的示例,因为它更易于理解。
2011年

6
如果您仍在寻找Applicative而不是Monad的类型构造函数,那么一个很常见的例子是ZipList
约翰L

23
_|_居住于*中的每种类型,但重点Void是您必须向后弯腰构造一个,否则您已经破坏了它的价值。这就是为什么它不是Enum,Monoid等的实例的原因。如果您已经拥有一个实例,很高兴让您将它们融合在一起(为您提供一个Semigroup),但是mempty,但是我没有提供任何工具来显式构造Voidin void。您必须装载枪并将其指向脚下并自己拉动扳机。
爱德华KMETT 2012年

2
在学上,我认为您对Cofunctor的看法是错误的。函子的对偶就是函子,因为同时翻转输入输出并最终得到相同的结果。您正在寻找的概念可能是“逆函子”,这略有不同。
本·米尔伍德

1
@AlexVong:“已弃用”->人们只是使用其他软件包。谈论“逆变函子”而不是“双重函子”,对此感到抱歉。在某些情况下,我已经看到“ cofunctor”曾经被称为“ contravariant functor”,因为functor是自对偶的,但似乎只会使人们感到困惑。
Dietrich Epp

87

手机可能会限制我的风格,但是可以。

newtype Not x = Kill {kill :: x -> Void}

不能是函子。如果是这样,我们将有

kill (fmap (const ()) (Kill id)) () :: Void

月亮将由绿色奶酪制成。

与此同时

newtype Dead x = Oops {oops :: Void}

是一个函子

instance Functor Dead where
  fmap f (Oops corpse) = Oops corpse

但不能适用,否则我们就会

oops (pure ()) :: Void

而格林将由月亮奶酪制成(实际上可能会发生,但仅限于晚上)。

(特别注意:Void,因为in Data.Void是一个空数据类型。如果您试图undefined用来证明它是Monoid,那么我将unsafeCoerce用来证明它不是。)

很高兴

newtype Boo x = Boo {boo :: Bool}

在许多方面都具有适用性,例如Dijkstra希望的那样,

instance Applicative Boo where
  pure _ = Boo True
  Boo b1 <*> Boo b2 = Boo (b1 == b2)

但它不能是Monad。要了解为什么不这样做,请注意返回值必须为Boo TrueBoo False,因此

join . return == id

无法容纳。

哦,是的,我差点忘了

newtype Thud x = The {only :: ()}

是一个Monad。自己动手。

赶飞机...


8
空洞是空的!道德上,无论如何。
Pigworker 2011年

9
我认为Void是具有0个构造函数的类型。它不是monoid,因为没有mempty
Rotsor

6
未定义?真没礼貌!可悲的是,unsafeCoerce(unsafeCoerce()<*> undefined)不是(),因此在现实生活中,有些观察结果违反​​了法律。
Pigworker 2011年

5
在通常的语义中,可以完全容忍一种未定义的语义,您说的很对。当然,还有其他语义。虚空不限于总片段中的亚类虚空。它也不是区分失败模式的语义中的一个monoid。当我有比基于电话的编辑更轻松的时间时,我将澄清一下,我的示例仅在没有完全一种未定义的语义的情况下起作用。
Pigworker 2011年

22
庸人自扰_|_
Landei

71

我相信其他答案错过了一些简单而常见的示例:

类型构造函数,它是Functor而不是Applicative。一个简单的例子是一对:

instance Functor ((,) r) where
    fmap f (x,y) = (x, f y)

但是如果Applicative不对施加其他限制,就无法定义其实例r。特别是,没有办法pure :: a -> (r, a)为任意定义r

类型构造器,它是一个Applicative,但不是Monad。一个著名的例子是ZipList。(这newtype是包装列表并为其提供不同Applicative实例的列表。)

fmap以通常的方式定义。但是pure<*>被定义为

pure x                    = ZipList (repeat x)
ZipList fs <*> ZipList xs = ZipList (zipWith id fs xs)

因此,pure通过重复给定值创建无限列表,并<*>用值列表压缩函数列表-将第i个函数应用于第i个元素。(标准<*>[]产生施加的所有可能的组合个函数来Ĵ个元素。)但是,没有合理的方式如何定义一个单子(参见此信息)。


箭头如何适合函子/应用/单子层次结构?萨姆·林德利,菲利普·沃德勒,杰里米·雅洛普 看到成语是遗忘的,箭是细致的,单子是混杂的。MSFP2008。(他们称其为应用函子习语。)摘要:

我们重新讨论了三种计算概念之间的联系:Moggi的单子,Hughes的箭头以及McBride和Paterson的成语(也称为应用函子)。我们证明成语等效于满足同构类型A〜> B = 1〜>(A-> B)的箭头,单子等效于满足同构类型A〜> B = A->(1〜 > B)。此外,成语嵌入箭头,而箭头嵌入单子。


1
因此((,) r),不是应用函数的函子也是如此。但这只是因为您通常无法一次pure全部定义r。因此,它是试图定义应用性函子的(无限)集合与一个定义语言简洁的怪癖,pure<*>; 从这个意义上讲,这个反例在数学上似乎没有什么深奥的东西,因为对于任何具体的例子r((,) r) 可以做成一个应用函子。问题:您能想到一个不能用作应用函数的CONCRETE函子吗?
乔治

1
有关此问题的信息,请参见stackoverflow.com/questions/44125484/…
乔治

20

一个不是函子的类型构造器的很好的例子是Set:您不能实现fmap :: (a -> b) -> f a -> f b,因为没有附加的约束Ord b就无法构造f b


16
实际上,这是一个很好的例子,因为从数学上讲,我们真的想使它成为仿函数。
Alexandre C.

21
@AlexandreC。我不同意这一点,这不是一个很好的例子。从数学上讲,这样的数据结构确实形成了函子。我们无法实现的事实fmap仅仅是语言/实现问题。另外,可以包装Set到延续monad中,这使monad具有我们期望的所有属性,请参见此问题(尽管我不确定它是否可以有效地完成)。
彼得·普德拉克(PetrPudlák)2012年

@PetrPudlak,这是一个语言问题吗?的相等性b可能不确定,在这种情况下,您无法定义fmap
Turion '18

@Turion可决定和可定义是两件不同的事情。例如,即使不能通过算法确定相等性,也可以在lambda项(程序)上正确定义相等性。无论如何,此示例都不是这种情况。这里的问题是我们不能Functor使用Ord约束定义实例,但是使用不同的定义Functor或更好的语言支持可能是可行的。实际上,使用ConstraintKinds 可以定义可以参数化的类型类。
PetrPudlák'18

即使我们可以克服这种ord限制,a Set不能包含重复的条目这一事实也意味着fmap可以破坏上下文。这违反了关联律。
约翰·米勒

11

我想提出一种更系统的方法来回答这个问题,并展示一些示例,这些示例不使用诸如“底”值或无限数据类型之类的任何特殊技巧。

类型构造函数何时不具有类型类实例?

通常,类型构造函数可能无法拥有某个类型类的实例有两个原因:

  1. 无法实现类型类中所需方法的类型签名。
  2. 可以实现类型签名,但不能满足必需的法律。

第一种示例比第二种示例容易,因为对于第一类,我们只需要检查一个人是否可以使用给定类型签名来实现一个函数,而对于第二种,则需要证明没有实现可能会满足法律。

具体例子

  • 由于无法实现类型而不能具有函子实例的类型构造函数

    data F z a = F (a -> z)

关于type参数a,这是一个矛盾的东西,而不是一个函子,因为它a处于矛盾的位置。用类型签名实现一个函数是不可能的(a -> b) -> F z a -> F z b

  • 即使fmap可以实现的类型签名,也不是合法的仿函数的类型构造函数

    data Q a = Q(a -> Int, a)
    fmap :: (a -> b) -> Q a -> Q b
    fmap f (Q(g, x)) = Q(\_ -> g x, f x)  -- this fails the functor laws!

这个示例的奇妙之处在于,即使它不可能用作函子,因为它在反位置使用,我们也可以实现fmap正确的类型。因此,上面显示的这种实现方式具有误导性-尽管它具有正确的类型签名(我相信这是该类型签名的唯一可能的实现方式),但并不满足函子定律。例如,≠ ,因为is ,但是is 。Fafmapfmap ididlet (Q(f,_)) = fmap id (Q(read,"123")) in f "456"123let (Q(f,_)) = id (Q(read,"123")) in f "456"456

实际上,F它只是一个发音器,它既不是函子也不是冲突器。

  • 由于pure无法实现的类型签名而无法应用的合法函子:采用Writer monad (a, w)并删除w应该为类半体的约束。它是那么不可能构造类型的值(a, w)出来的a

  • 由于<*>无法实现的类型签名而无法应用的函子data F a = Either (Int -> a) (String -> a)

  • 即使可以实现类型类方法的函子也不是合法的

    data P a = P ((a -> Int) -> Maybe a)

类型构造函数P是一个函子,因为它a仅在协变位置使用。

instance Functor P where
   fmap :: (a -> b) -> P a -> P b
   fmap fab (P pa) = P (\q -> fmap fab $ pa (q . fab))

类型签名的唯一可能的实现<*>是一个始终返回的函数Nothing

 (<*>) :: P (a -> b) -> P a -> P b
 (P pfab) <*> (P pa) = \_ -> Nothing  -- fails the laws!

但是,此实现不满足适用函子的身份定律。

  • Applicative不能但不是的函子,Monad因为bind无法实现的类型签名。

我不知道任何这样的例子!

  • 算符是Applicative,但不是Monad因为法律不能满足,即使类型签名bind可以实现。

这个例子引起了很多讨论,因此可以肯定地说,证明这个例子正确并不容易。但是有几个人通过不同的方法独立地对此进行了验证。请参阅“数据PoE是否为空”?配对一个单子吗?进行其他讨论。

 data B a = Maybe (a, a)
   deriving Functor

 instance Applicative B where
   pure x = Just (x, x)
   b1 <*> b2 = case (b1, b2) of
     (Just (x1, y1), Just (x2, y2)) -> Just((x1, x2), (y1, y2))
     _ -> Nothing

证明没有合法的Monad事例有点麻烦。非monadic行为的原因是,没有自然的方式可以实现bind函数何时f :: a -> B b可以返回NothingJust针对的不同值a

考虑一下Maybe (a, a, a),它也不是monad,然后尝试实现它,可能更清楚join。人们会发现,没有直观上合理的实现方式join

 join :: Maybe (Maybe (a, a, a), Maybe (a, a, a), Maybe (a, a, a)) -> Maybe (a, a, a)
 join Nothing = Nothing
 join Just (Nothing, Just (x1,x2,x3), Just (y1,y2,y3)) = ???
 join Just (Just (x1,x2,x3), Nothing, Just (y1,y2,y3)) = ???
 -- etc.

在所示的情况下???,显然我们不能Just (z1, z2, z3)以任何合理和对称的方式从六个不同的type值中产生a。我们当然可以从这六个值中选择任意子集,例如,始终取第一个非空值,Maybe但这不能满足单子法则。归还Nothing也将不符合法律。

  • 一个树状的数据结构,即使具有与之的关联性,也不是monadbind但不符合身份定律。

通常的树状单子(或“带有函子形分支的树”)定义为

 data Tr f a = Leaf a | Branch (f (Tr f a))

这是对函子的免费单子f。数据的形状是一棵树,其中每个分支点都是一个“功能丰富的”子树。标准的二叉树将通过获得type f a = (a, a)

如果我们也通过以f仿函数的形式制作叶子来修改此数据结构,则会得到我称为“ semimonad”的信息,它具有bind满足自然性和关联性定律的要求,但是其pure方法不能满足同一性定律之一。“ Semimonads是endofunctors类别中的半群,这是什么问题?” 这是类型类Bind

为简单起见,我定义join方法而不是bind

 data Trs f a = Leaf (f a) | Branch (f (Trs f a))
 join :: Trs f (Trs f a) -> Trs f a
 join (Leaf ftrs) = Branch ftrs
 join (Branch ftrstrs) = Branch (fmap @f join ftrstrs)

分枝嫁接是标准的,但叶嫁接是非标准的,并产生Branch。这对于关联性法则不是问题,但是打破了身份法则之一。

多项式类型何时具有monad实例?

函子Maybe (a, a)Maybe (a, a, a)都不能被判为合法Monad,尽管它们显然是Applicative

这些仿函数没有技巧-没有Void或没有任何技巧bottom,没有棘手的懒惰/严格性,没有无限的结构,没有类型类约束。该Applicative实例是完全标准的。功能returnbind这些函子可以实现,但不满足单子法则。换句话说,这些函子不是单子,因为缺少了特定的结构(但是不容易理解到底缺少了什么)。例如,在函子上进行很小的更改就可以使其成为monad:data Maybe a = Nothing | Just a是monad。另一个类似的函子data P12 a = Either a (a, a)也是monad。

多项式monad的构造

通常,这里有一些构造可以Monad从多项式类型中产生合法的。在所有这些构造中,M一个单子是:

  1. type M a = Either c (w, a)w什么半身像在哪里
  2. type M a = m (Either c (w, a))这里m是任何单子,并w为任何幺
  3. type M a = (m1 a, m2 a)这里m1m2任何单子
  4. type M a = Either a (m a)m单子在哪里

第一个结构是WriterT w (Either c),第二个结构是WriterT w (EitherT c m)。第三结构是单子的成分之积:pure @M被定义为的成分之积pure @m1pure @m2,并join @M通过省略跨产品数据(例如定义m1 (m1 a, m2 a)映射到m1 (m1 a)通过省略元组的第二部分):

 join :: (m1 (m1 a, m2 a), m2 (m1 a, m2 a)) -> (m1 a, m2 a)
 join (m1x, m2x) = (join @m1 (fmap fst m1x), join @m2 (fmap snd m2x))

第四种结构定义为

 data M m a = Either a (m a)
 instance Monad m => Monad M m where
    pure x = Left x
    join :: Either (M m a) (m (M m a)) -> M m a
    join (Left mma) = mma
    join (Right me) = Right $ join @m $ fmap @m squash me where
      squash :: M m a -> m a
      squash (Left x) = pure @m x
      squash (Right ma) = ma

我检查了所有四个构造都产生合法的单子。

猜想多项式单子没有其他构造。例如,函子Maybe (Either (a, a) (a, a, a, a))不是通过任何这些构造获得的,因此不是单子函数。然而,Either (a, a) (a, a, a)是一元,因为它是同构于三个单子的产品aaMaybe a。另外,Either (a,a) (a,a,a,a)是一元,因为它是同构的产品aEither a (a, a, a)

上面显示的四种构造将使我们能够获得任何数量的任意数量的产品的总和a,例如Either (Either (a, a) (a, a, a, a)) (a, a, a, a, a))以此类推。所有此类类型构造函数都将具有(至少一个)Monad实例。

当然,此类单子可能存在哪些用例仍然有待观察。另一个问题是,Monad通过构造1-4派生的实例通常不是唯一的。例如,type F a = Either a (a, a)可以通过Monad两种方式为类型构造函数提供实例:通过使用monad的构造4 (a, a)以及通过类型同构的构造3 Either a (a, a) = (a, Maybe a)。同样,为这些实现找到用例并不是立即显而易见的。

问题仍然存在-给定任意多项式数据类型,如何识别它是否具有Monad实例。我不知道如何证明多项式单子没有其他构造。我认为到目前为止,还没有任何理论可以回答这个问题。


我认为B 单子。您可以对此绑定给出反例Pair x y >>= f = case (f x, f y) of (Pair x' _,Pair _ y') -> Pair x' y' ; _ -> Empty吗?
Franky

@Franky关联性失败,当你选择这个定义f,这样f xEmpty,但f yPair,并在接下来的步骤都是Pair。我亲手检查了该实施或任何其他实施的法律不成立。但这是一项相当大的工作。我希望有一个更简单的方法来解决这个问题!
winitzki

1
@Turion该参数不适用于,Maybe因为Maybe它不包含a担心的不同值。
丹尼尔·瓦格纳

1
@Turion我通过几页的计算证明了这一点;关于“自然方式”的争论只是一种启发式的解释。一个Monad实例包含的功能returnbind满足法律。有2种实现return和25种实现bind符合所需类型。您可以通过直接计算来证明没有实现符合法律要求。为了减少所需的工作量,我首先使用join而不是bind使用身份法。但这是一项相当大的工作。
winitzki '18

1
@duplode不,我认为Traversable不需要。m (Either a (m a))使用pure @m转换为m (Either (m a) (m a))。然后琐碎Either (m a) (m a) -> m a,我们可以使用join @m。那是我检查法律的实施方式。
winitzki
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.