相关问题是
第一个问题:
Monads有什么特别之处?
monad是一种数学结构,基本上在(纯)函数式编程中使用,基本上是Haskell。但是,还有许多其他数学结构可用,例如,应用函子,强单子或单半体。有些具有更具体的含义,有些则更通用。然而,单子更受欢迎。为什么会这样?
回答问题的评论:
据我所知,单子被Wadler所普及,当时没有乏味的CPS进行IO和没有明确的状态传递就进行解析的想法是巨大的卖点。那是一个非常激动人心的时刻。 A.F.A.I.R.,Haskell没有构造函数类,但是Gofer(Hugs的父亲)做了。 Wadler提出了monad的重载列表理解功能,因此后来出现了do表示法。一旦IO变成了monadic,monads对初学者来说就变得很重要,巩固了他们作为grok的主要目标。可能的话,应用程序要好得多,Arrows则更通用,但后来出现了,IO很难销售单子。 – AndrewC '13年5月9日,下午1:34
@Conal的答案是:
我怀疑与其他许多类型相比,对这一特定类型类(
Monad
)的过多关注主要是历史上的fl幸。人们经常将IO
与Monad
相关联,尽管两者是独立有用的想法(as are list reversal and bananas)。因为IO
是神奇的(具有实现但没有符号),并且Monad
通常与IO
相关联,所以很容易陷入关于Monad
的神奇思考。
首先,我同意他们的观点,我认为Monads的用处主要来自Functors,因为我们可以在结构中嵌入许多功能,而Monads则通过join
扩展了功能组合的健壮性:M(M(X)) -> M(X)
以避免嵌套类型。
第二个问题:
我们必须使用一元函数a-> m b吗?
如此多的网上教程仍然坚持使用monadic函数,因为那是Kleisli三元组和monad-laws。
还有许多类似的答案
我喜欢将m理解为“计划获得”的意思,其中“计划”涉及除纯计算之外的某种其他交互作用。
或
在不需要
Monad
的情况下,通常更简单地使用Applicative
,Functor
或仅使用基本的纯函数。在这些情况下,应该(通常)使用这些东西来代替Monad
。例如:
ws <- getLine >>= return . words -- Monad
ws <- words <$> getLine -- Functor (much nicer)
要清楚:如果没有monad可以实现,并且如果没有monad则更简单易读,那么您应该在没有monad的情况下做到!如果monad使代码变得比所需复杂或混乱,请不要使用monad! Haskell具有monad的唯一目的是使某些复杂的计算更简单,更易于阅读且更易于推理。如果这没有发生,则您不应该使用monad 。
阅读他们的答案,我想他们对Monad的特殊感来自历史事件,即Haskell社区碰巧选择了Kleisli类别的Monad来解决他们的问题(IO等)
所以,再次,我认为Monads的有用性主要来自Functors,我们可以在结构中嵌入许多功能,而Monads则通过join
:M(M(X)) -> M(X)
扩展了功能组合的健壮性。避免使用嵌套类型。
事实上,在JavaScript中,我是按以下方式实现的。
console.log("Functor");
{
const unit = (val) => ({
// contextValue: () => val,
fmap: (f) => unit((() => {
//you can do pretty much anything here
const newVal = f(val);
// console.log(newVal); //IO in the functional context
return newVal;
})()),
});
const a = unit(3)
.fmap(x => x * 2) //6
.fmap(x => x + 1); //7
}
关键是我们可以在Functor结构中实现我们喜欢的任何东西,在这种情况下,我只是将其设置为IO / console.log
的值。
另一点是,绝对不需要这样做Monads。
现在,基于上面的Functor实现,我添加了额外的join: MMX => MX
功能来避免嵌套结构,该结构应该有助于复杂功能组合的鲁棒性。
该功能与上面的Functor完全相同,请注意用法也与Functor fmap
相同。这不需要bind
的“单子函数”(单子的Kleisli组成)。
console.log("Monad");
{
const unit = (val) => ({
contextValue: () => val,
bind: (f) => {
//fmap value operation
const result = (() => {
//you can do pretty much anything here
const newVal = f(val);
console.log(newVal);
return newVal;
})();
//join: MMX => MX
return (result.contextValue !== undefined)//result is MX
? result //return MX
: unit(result) //result is X, so re-wrap and return MX
}
});
//the usage is identical to the Functor fmap.
const a = unit(3)
.bind(x => x * 2) //6
.bind(x => x + 1); //7
}
以防万一,此Monad的实现满足monad法则,而上面的Functor不满足。
console.log("Monad laws");
{
const unit = (val) => ({
contextValue: () => val,
bind: (f) => {
//fmap value operation
const result = (() => {
//you can do pretty much anything here
const newVal = f(val);
//console.log(newVal);
return newVal;
})();
//join: MMX => MX
return (result.contextValue !== undefined)
? result
: unit(result)
}
});
const M = unit;
const a = 1;
const f = a => (a * 2);
const g = a => (a + 1);
const log = m => console.log(m.contextValue()) && m;
log(
M(f(a))//==m , and f is not monadic
);//2
console.log("Left Identity");
log(
M(a).bind(f)
);//2
console.log("Right Identity");
log(
M(f(a))//m
.bind(M)// m.bind(M)
);//2
console.log("Associativity");
log(
M(5).bind(f).bind(g)
);//11
log(
M(5).bind(x => M(x).bind(f).bind(g))
);//11
}
所以,这是我的问题。
我可能是错的。
是否有任何反例表明Functors除了通过使嵌套结构变平坦来增强功能组合的健壮性之外,不能做Monads可以做的事情?
Kleisli类别的Monads有何特别之处?似乎有可能通过稍微扩展来实现Monad来避免Functor的嵌套结构,并且不使用属于Kleisli类别的实体的monadic函数a -> m b
。
谢谢。
编辑(2018-11-01)
阅读答案,我同意在IdentityFunctor中执行应该满足Functor律的console.log
是不合适的,所以我像Monad代码一样注释掉了。
因此,消除该问题,我的问题仍然存在:
是否有任何反例表明Functors除了通过使嵌套结构变平坦来增强功能组合的健壮性之外,无法执行Monads的功能?
Kleisli类别的Monad有何特别之处?似乎有可能通过稍微扩展来实现Monad来避免Functor的嵌套结构,并且不使用属于Kleisli类别中实体的monadic函数a -> m b
。
@DarthFennec的回答是:
“避免嵌套类型”实际上并不是
join
的目的,它只是一个很好的副作用。使用它的方式听起来像join
只是剥离了外部类型,但monad的值不变。
我相信“避免嵌套类型”不仅是一个整洁的副作用,而且是范畴论中Monad的“ join”定义,
单子的乘法自然变换μ:T∘T⇒T为每个对象X提供一个态射μX:T(T(X))→T(X)
monad (in computer science): Relation to monads in category theory
这正是我的代码所做的。
另一方面,
情况并非如此。
join
是monad的心脏,这就是让monad 做事的原因。
我知道很多人以这种方式在Haskell中实现monad,但事实是,在Haskell中有也许仿函数,它没有join
,或者有免费的monad ,join
从一开始就嵌入到已定义的结构中。它们是用户定义执行任务的功能键 的对象。
因此
您可以认为函子基本上是一个容器。有一个任意的内部类型,它周围是一个外部结构,该结构允许一些差异,一些额外的值来“装饰”您的内部值。
fmap
允许您以正常方式处理容器内部的内容。这基本上是您可以使用函子的限制。monad是具有特殊功能的函子:
fmap
允许您处理内部值,bind
允许您以一致的方式组合外部值 。这比简单的函子要强大得多。
这些观察结果不符合Maybe函子和Free monad的存在。
答案 0 :(得分:3)
关键是我们可以在Functor结构中实现我们喜欢的任何东西,在这种情况下,我只是将其设置为IO /
console.log
的值。另一点是,绝对不需要这样做Monads。
问题在于,一旦执行此操作,函子就不再是函子。函子应保留身份和组成。对于Haskell Functor
,这些要求总计为:
fmap id = id
fmap (g . f) = fmap g . fmap f
这些法律可以保证所有fmap
都在使用提供的函数来修改值-它不会在背后做任何有趣的事情。对于您的代码,fmap(x => x)
不应执行任何操作;而是打印到控制台。
请注意,以上所有内容均适用于IO
仿函数:如果a
是IO
动作,则执行fmap f a
不会产生I / O效果a
已经拥有。在精神上写出与您的代码相似的东西的一个刺痛可能是...
applyAndPrint :: Show b => (a -> b) -> a -> IO b
applyAndPrint f x = let y = f x in fmap (const y) (print y)
pseudoFmap :: Show b => (a -> b) -> IO a -> IO b
pseudoFmap f a = a >>= applyAndPrint f
...但是已经使用Monad
了,因为我们有一个效果(打印结果)取决于先前的计算结果。
不用说,如果您如此倾向于(并且您的类型系统允许),您可以编写忽略所有这些区别的代码。但是,需要权衡取舍:Functor
相对于Monad
的降低的功能还额外保证了使用该接口可以执行和不能执行的功能-这就是使区别有用的原因首先。
答案 1 :(得分:3)
是否有任何反例表明Functors除了通过使嵌套结构变平坦来增强功能组合的健壮性之外,不能做Monads可以做的事情?
我认为这很重要:
Monads通过加入
M(M(X)) -> M(X)
来扩大函数组合的健壮性,以避免嵌套类型。
“避免嵌套类型”实际上并不是join
的目的,它只是一个很好的副作用。使用它的方式听起来像join
只是剥离了外部类型,但monad的值不变。不是这种情况。 join
是monad的心脏,这就是让monad 做事的原因。
您可以认为函子基本上是一个容器。有一个任意的内部类型,它周围是一个外部结构,该结构允许一些差异,一些额外的值来“装饰”您的内部值。 fmap
允许您以正常方式处理容器内部的内容。这基本上是您可以使用函子的限制。
monad是具有特殊功能的函子:fmap
允许您处理内部值,bind
允许您以一致的方式组合外部值 。这比简单的函子要强大得多。
关键是我们可以在Functor结构中实现我们喜欢的任何东西,在这种情况下,我只是将其设置为IO /
console.log
的值。
这实际上是不正确的。您能够在此处进行IO的唯一原因是因为您正在使用Javascript,并且可以在任何地方进行IO。在像Haskell这样的纯粹的功能语言中,无法使用像这样的函子来完成IO。
这是一个粗略的概括,但是在大多数情况下,将IO
描述为美化的State
单子很有用。每个IO
动作都带有一个称为RealWorld
(代表现实世界的状态)的额外隐藏参数,可以从中读取或修改它,然后将其发送到下一个IO
行动。此RealWorld
参数穿过链。如果将某些内容写入屏幕,则RealWorld
将被复制,修改和传递。但是“相处”如何工作?答案是join
。
假设我们要从用户那里读取一行,并将其打印回屏幕:
getLine :: IO String
putStrLn :: String -> IO ()
main :: IO ()
main = -- ?
让我们假设IO
是仿函数。我们如何实现呢?
main :: IO (IO ())
main = fmap putStrLn getLine
在这里,我们将putStrLn
提升到IO
,以获得fmap putStrLn :: IO String -> IO (IO ())
。如果您还记得,putStrLn
会使用String
和隐藏的RealWorld
并返回修改后的RealWorld
,其中String
参数为打印到屏幕上。我们使用fmap
取消了此功能,因此它现在需要一个IO
(这是一个操作,它需要一个隐藏的RealWorld
,并返回修改后的RealWorld
和一个String
,并返回相同io操作,只是包裹在另一个值上(一个完全独立的操作,该操作也需要一个单独的隐藏RealWorld
并返回一个{{1} }。即使在将RealWorld
应用于此功能之后,实际上也不会发生任何事情或将其打印出来。
我们现在有一个getLine
。此操作需要一个隐藏的main :: IO (IO ())
,并返回修改后的RealWorld
和一个单独的操作。第二个动作采用不同的RealWorld
,并返回另一个修改后的RealWorld
。它本身是没有意义的,它不会给您任何东西,也不会在屏幕上打印任何内容。需要发生的是,两个RealWorld
动作需要连接在一起,以便一个动作返回的IO
被作为另一个动作的RealWorld
参数输入。这样,它变成了RealWorld
的一个连续链,随着时间的流逝而变异。当两个RealWorld
操作与IO
合并时,就会发生这种“连接”或“链接”。
当然,join
会根据您使用的单子而做不同的事情,但是对于join
和IO
型单子来说,这或多或少是在罩。在很多情况下,您所做的非常简单的操作不需要State
,在这些情况下,将monad视为函子或应用函子很容易。但是通常这还不够,在这种情况下,我们使用单子。
编辑:对评论和已编辑问题的回复:
我没有看到在categort理论中对Monads的任何定义可以解释这一点。我读到的所有关于join的内容都是stil
join
,而这正是我的代码所做的。
您还能准确说明函数MMX => MX
的作用吗?可能不按原样返回输入,反转,过滤,追加,忽略它并返回完全不同的值,或者导致String -> String
的其他任何结果吗?类型不能确定函数的功能,它会限制函数的功能。由于String
通常仅由其类型定义,因此任何特定的monad都可以执行该类型允许的任何操作。这可能只是剥离外层,或者可能是将两层合并为一层的极其复杂的方法。只要从两层开始,到最后一层,都没有关系。这种类型提供了多种可能性,这是使monad如此强大的原因之一。
Haskell中有MaybeFunctor。那里没有“加入”或“绑定”,我想知道力量从何而来。 MaybeFunctor和MaybeMonad有什么区别?
每个monad都是函子:monad仅仅是具有join
功能的函子。如果您将join
或join
与bind
一起使用,则您将其用作单子,并且具有单子的全部功能。如果您不使用Maybe
或join
,而仅使用bind
和fmap
,则表示您将其用作函子,并且它仅限于做仿函数可以做到。如果没有pure
或join
,则没有额外的单子电源。
我相信“避免嵌套类型”不仅是整洁的副作用,而且是范畴论中Monad的“ join”的定义
bind
的定义是从嵌套monad到非嵌套monad的转换。同样,这可能意味着任何内容。说join
的目的是“避免嵌套类型”,就像说join
的目的是避免数字对。大多数操作以某种方式组合事物,但是仅出于将事物组合在一起的目的而存在的操作很少。重要的是结合如何发生。
在Haskell中有也许是仿函数,它没有
+
,或者有 Free monad 是从第一个嵌入的join
放入定义的结构。它们是用户定义执行任务的功能键 的对象。
我已经讨论过join
,以及当您仅将其用作函子时,如何将其用作monad不能做它能做的事情。 Maybe
很奇怪,因为它是实际上不做任何事情的少数几个单子。
Free
可用于将任何仿函数转换为monad,这使您可以使用Free
表示法和其他便利。但是,do
的局限性在于,Free
不会像其他monad那样组合您的动作,而是将它们分开,将它们插入类似列表的结构中;想法是稍后处理此结构,并通过单独的代码组合操作。一种等效的方法是将处理代码本身移至join
中,但这会将函子变成monad,并且使用join
毫无意义。所以Free
起作用的唯一原因是因为它在其他地方委托了monad的实际“做事”部分;其Free
选择将操作推迟到在monad外部运行的代码。这就像一个join
运算符,它不添加数字,而是返回一个抽象语法树。然后可以用任何需要的方式稍后处理该树。
这些观察结果不符合Maybe函子和Free monad的存在。
您不正确。如前所述,+
和Maybe
非常适合我以前的观察结果:
Free
仿函数与Maybe
单子表达式根本不具有相同的表现力。Maybe
monad只能以可能的唯一方式将仿函数转换为monad:通过不实现monadic行为,而只是将其推迟到某些假定的处理代码中。答案 2 :(得分:1)
您的“ functor”显然不是仿函数,同时违反了身份和组成法则:
console.log("Functor");
{
const unit = (val) => ({
// contextValue: () => val,
fmap: (f) => unit((() => {
//you can do pretty much anything here
const newVal = f(val);
console.log(newVal); //IO in the functional context
return newVal;
})()),
});
console.log("fmap(id) ...");
const a0 = unit(3)
.fmap(x => x); // prints something
console.log(" ≡ id?");
const a1 = (x => x)(unit(3)); // prints nothing
console.log("fmap(f) ∘ fmap(g) ...");
const b0 = unit(3)
.fmap(x => 3*x)
.fmap(x => 4+x); // prints twice
console.log(" ≡ fmap(f∘g)?");
const b1 = unit(3)
.fmap(x => 4+(3*x)); // prints once
}
答案 3 :(得分:1)
评论太长
我建议暂时不要忘记Kleisli类别;我认为他们与您的困惑无关。
虽然我仍然不完全理解您的问题和主张,但有些上下文可能会有用:类别理论非常笼统和抽象; haskell中存在的Monad
和Functor
之类的概念(必然)有些 less 通用和 less 抽象(例如,类别的概念) “ Hask”)。
一般来说,事物变得越具体(越不抽象),您拥有的力量就越多:如果我告诉您拥有车辆,那么您知道自己拥有可以将您从一个地方带到另一个地方的东西,但是您不知道它有多快,您是否知道它是否可以在陆地上行驶,等等。如果我告诉您有一艘快艇,那么您可以做的事和推理的整个世界就更大了(您可以用它抓鱼,您知道它不会把您从纽约带到丹佛)。
当你说:
Kleisli类别的Monads有何特别之处?
......我相信您犯了一个错误,即怀疑haskell中Monad
和Functor
的概念在某种程度上相对于类别理论具有限制性但是,正如我试图通过上面的类比解释一样,情况恰恰相反。
您的代码也有类似的错误思维:您对快艇(这是一种车辆)进行建模,并声称它表明所有车辆都快并且可以在水上行驶。