ChatGPT解决这个技术问题 Extra ChatGPT

Not a Functor/Functor/Applicative/Monad 的好例子?

在向某人解释类型类 X 是什么时,我很难找到恰好是 X 的数据结构的好例子。

因此,我请求以下示例:

不是 Functor 的类型构造函数。

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

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

一个类型构造函数,它是一个 Monad。

我认为到处都有很多 Monad 的例子,但是一个很好的 Monad 例子与前面的例子有一些关系可以完成图片。

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

如果有人能设法在这个层次结构中的某个地方偷偷地找到一个 Arrow 的例子(它是在 Applicative 和 Monad 之间吗?),那也太棒了!

是否可以创建一个存在 no 适合 fmap 的类型构造函数 (* -> *)?
Owen,我认为 a -> String 不是函子。
@Rotsor @Owen a -> String 是一个数学函子,但不是 Haskell Functor,要清楚。
@J。亚伯拉罕森,那么它在什么意义上是数学函子?您是在谈论箭头反转的类别吗?
人们不知道,逆变函子有一个 (a -> b) -> f b -> f a 类型的 fmap

D
Dietrich Epp

不是 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

一个类型构造函数,它是一个仿函数,但不是 Applicative:

我没有一个很好的例子。有 Const,但理想情况下,我想要一个具体的非 Monoid,但我想不出。当您深入了解时,所有类型基本上都是数字、枚举、乘积、总和或函数。您可以在下面看到 pigworker 和我不同意 Data.Void 是否是 Monoid

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

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

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

我想知道是否可以使用更丰富的类型系统创建这样的类型构造函数,例如带有各种扩展的 Agda 或 Haskell。

一个类型构造函数,它是 Applicative,但不是 Monad:

newtype T a = T {multidimensional array of a}

你可以用它来做一个应用程序,比如:

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

但是如果你把它变成一个单子,你可能会得到一个维度不匹配。我怀疑这样的例子在实践中很少见。

一个类型构造函数,它是一个 Monad:

[]

关于箭头:

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

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

但,

Arrow :: * -> * -> *

好清单!我建议使用像 Either a 这样更简单的东西作为最后一种情况的示例,因为它更容易理解。
如果您仍在寻找 Applicative 但不是 Monad 的类型构造函数,一个非常常见的示例是 ZipList
_|_ 存在于 * 中的所有类型中,但 Void 的要点是您必须向后弯腰才能构造一个,否则您已经破坏了它的值。这就是为什么它不是 Enum、Monoid 等的实例。如果你已经有一个,我很高兴让你将它们混合在一起(给你一个 Semigroup)但是 mempty,但我没有明确给出工具在 void 中构造一个 Void 类型的值。您必须将枪装上子弹并将其指向您的脚并自己扣动扳机。
迂腐地,我认为您对 Cofunctor 的概念是错误的。函子的对偶是函子,因为您将输入和输出都翻转并最终得到相同的东西。您正在寻找的概念可能是“逆变函子”,它略有不同。
@DietrichEpp:我认为这种用法不正确(没有引用),如果他们同意我的观点,请使用 asked math.SE
C
Cactus

我的风格可能会被我的手机束缚,但就这样吧。

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,因为 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 :: ()}

是一个单子。自己滚。

飞机要赶...


虚空是空的!道德上,无论如何。
我假设 Void 是一个有 0 个构造函数的类型。它不是幺半群,因为没有 mempty
不明确的?真没礼貌!可悲的是,unsafeCoerce (unsafeCoerce () <*> undefined) 不是 (),所以在现实生活中,存在违反法律的观察。
在通常的语义中,它恰好容忍一种未定义的,你是对的。当然,还有其他语义。 Void 不限于总片段中的亚类。它也不是区分失败模式的语义中的幺半群。当我有一个比基于电话的编辑更容易的时刻时,我将澄清我的示例仅适用于没有完全一种未定义的语义。
@Dietrich Epp IMO,应用定律是为 Haskell 指称语义的总值的 PER 编写的。请参阅Fast and Loose Reasoning is Morally Correct
P
Petr

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

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

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

但是,如果不对 r 施加额外限制,就无法定义它的 Applicative 实例。特别是,无法为任意 r 定义 pure :: a -> (r, a)

一个类型构造函数,它是一个 Applicative,但不是 Monad。 一个众所周知的例子是 ZipList。 (它是一个包装列表并为它们提供不同 Applicative 实例的 newtype。)

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

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

所以 pure 通过重复给定的值创建一个无限列表,并且 <*> 用一个值列表压缩一个函数列表 - 将第 i 个函数应用于 i -th 元素。 ([] 上的标准 <*> 产生了将第 i 个函数应用于第 j 个元素的所有可能组合。)但是没有明智的方法来定义一个单子(见 this post)。

箭头如何适应函子/应用程序/monad 层次结构?参见 Sam Lindley、Philip Wadler、Jeremy Yallop 的Idioms are oblivious, arrows are meticulous, monads are promiscuous。 MSFP 2008。(他们称应用函子 idioms。)摘要:

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


所以 ((,) r) 是一个不是应用程序的函子;但这仅仅是因为您通常不能一次为所有 r 定义 pure。因此,这是一种语言简洁的怪癖,试图用 pure<*> 的一个定义来定义一个(无限的)应用函子集合;从这个意义上说,这个反例似乎没有任何数学深度,因为对于任何具体的 r((,) r) 可以成为一个应用函子。问题:你能想到一个不能成为应用程序的具体函子吗?
请参阅 stackoverflow.com/questions/44125484/… 作为包含此问题的帖子。
@George,不,这不是真的。如果 r 中没有任何元素,即使您可以提供 fmap,您也无法为此实现 pure
L
Landei

不是函子的类型构造函数的一个很好的例子是 Set:你不能实现 fmap :: (a -> b) -> f a -> f b,因为没有额外的约束 Ord b 你不能构造 f b


这实际上是一个很好的例子,因为在数学上我们真的很想把它变成一个函子。
@AlexandreC。我不同意这一点,这不是一个很好的例子。从数学上讲,这样的数据结构确实形成了函子。我们无法实现 fmap 的事实只是语言/实现问题。此外,还可以将 Set 包装到 continuation monad 中,从而将其制成具有我们期望的所有属性的 monad,请参阅 this question(尽管我不确定它是否可以有效地完成)。
@PetrPudlak,这是一个语言问题吗? b 的相等性可能无法确定,在这种情况下您不能定义 fmap
@Turion 可判定和可定义是两件不同的事情。例如,可以在 lambda 项(程序)上正确定义相等性,即使不可能通过算法来决定它。无论如何,这不是这个例子的情况。这里的问题是我们无法使用 Ord 约束来定义 Functor 实例,但可以使用 Functor 的不同定义或更好的语言支持。实际上用 ConstraintKinds it is possible 来定义一个可以像这样参数化的类型类。
即使我们可以克服 ord 约束,Set 不能包含重复条目这一事实也意味着 fmap 可以祭祀上下文。这违反了结合律。
w
winitzki

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

类型构造函数什么时候没有类型类实例?

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

无法从类型类实现所需方法的类型签名。可以实现类型签名但不能满足所需的规律。

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

具体例子

由于无法实现类型而不能具有仿函数实例的类型构造函数:data F za = F (a -> z)

对于类型参数 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(\_ -> gx, fx) - 这不符合函子定律!

这个例子的奇怪之处在于我们可以实现正确类型的 fmap,即使 F 不可能是函子,因为它在逆变位置使用 a。因此,上面显示的 fmap 的这种实现具有误导性——即使它具有正确的类型签名(我相信这是该类型签名的唯一可能实现),但函子定律并不满足。例如,fmap idid,因为 let (Q(f,_)) = fmap id (Q(read,"123")) in f "456"123,而 let (Q(f,_)) = id (Q(read,"123")) in f "456"456

事实上,F 只是一个函子,它既不是函子也不是反函子。

一个不适用的合法函子,因为 pure 的类型签名无法实现:采用 Writer monad (a, w) 并删除 w 应该是一个幺半群的约束。这样就不可能从 a 中构造一个类型为 (a, w) 的值。

由于无法实现 <*> 的类型签名而不适用的函子: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 的类型签名,也无法满足规则。

这个例子引起了相当多的讨论,所以可以肯定地说,证明这个例子是正确的并不容易。但有几个人通过不同的方法独立验证了这一点。有关其他讨论,请参见 Is `data PoE a = Empty | Pair a a` a monad?

 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 实例有点麻烦。非单子行为的原因是,当函数 f :: a -> B b 可以针对不同的 a 值返回 NothingJust 时,没有自然的方式来实现 bind

考虑 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.

??? 所指出的情况下,显然我们不能以任何合理和对称的方式从 a 类型的六个不同值中产生 Just (z1, z2, z3)。我们当然可以选择这六个值的任意子集——例如,总是取第一个非空的 Maybe——但这不满足单子定律。返回 Nothing 也不会满足法律。

一种树状数据结构,它不是单子,即使它具有绑定的关联性 - 但不符合恒等律。

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

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

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

如果我们修改这个数据结构,也将叶子做成仿函数 f 的形状,我们会得到我所说的“半单子”——它有 bind,它满足自然性和结合律,但它的 pure方法不符合身份定律之一。 “半单子是内函子范畴中的半群,有什么问题?”这是类型类 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。这对结合律来说不是问题,但违反了恒等律之一。

多项式类型什么时候有单子实例?

仿函数 Maybe (a, a)Maybe (a, a, a) 都不能被赋予合法的 Monad 实例,尽管它们显然是 Applicative

这些函子没有任何技巧 - 任何地方都没有 Voidbottom,没有棘手的惰性/严格性,没有无限结构,也没有类型类约束。 Applicative 实例是完全标准的。函数 returnbind 可以为这些函子实现,但不满足单子定律。换句话说,这些函子不是 monad,因为缺少特定的结构(但很难理解到底缺少了什么)。例如,函子中的一个小改动可以使它变成一个 monad:data Maybe a = Nothing | Just a 是一个 monad。另一个类似的仿函数 data P12 a = Either a (a, a) 也是一个 monad。

多项式单子的构造

一般来说,这里有一些从多项式类型中产生合法 Monad 的结构。在所有这些结构中,M 是一个单子:

类型 M a = Either c (w, a) 其中 w 是任何幺半群类型 M a = m (Either c (w, a)) 其中 m 是任何单子并且 w 是任何幺半群类型 M a = (m1 a, m2 a ) 其中 m1 和 m2 是任何单子类型 M a = Either a (ma) 其中 m 是任何单子

第一个结构是WriterT w (Either c),第二个结构是WriterT w (EitherT c m)。第三种构造是 monad 的组件式乘积: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 实例。

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

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


我认为 B monad。你能给出这个绑定Pair x y >>= f = case (f x, f y) of (Pair x' _,Pair _ y') -> Pair x' y' ; _ -> Empty的反例吗?
@Turion 该论点不适用于 Maybe 因为 Maybe 不包含不同的 a 值来担心。
@Turion我通过几页计算证明了这一点;关于“自然方式”的争论只是一种启发式的解释。 Monad 实例由满足法律的函数 returnbind 组成。 return 有两个实现和 bind 的 25 个实现适合所需的类型。您可以通过直接计算表明没有一个实现满足法律。为了减少所需工作量,我使用 join 而不是 bind 并首先使用恒等律。但这是一项相当大的工作。
@duplode 不,我认为不需要 Traversablem (Either a (m a)) 使用 pure @m 转换为 m (Either (m a) (m a))。然后简单地Either (m a) (m a) -> m a,我们可以使用join @m。那是我检查法律的实施。
@Qqwy 是的,这是一个很好的例子。它对应于一个常量仿函数(不依赖于它的类型参数)。常量函子只有在它们是 Unit 类型时才是 monad。