我对绑定(bind)函数的定义有一些疑问(>>=)
在 haskell 。
因为 Haskell 是纯语言,所以我们可以使用 Monad 来处理有副作用的操作。我觉得这个策略有点像把所有的 Action 都可能对另一个世界造成副作用,我们可以通过我们的“纯”haskell 世界来控制它们虽然do
或 >>=
.
因此,当我查看 >>=
的定义时功能
(>>=) :: Monad m => m a -> (a -> m b) -> m b
它需要一个
(a -> m b)
函数,所以结果 m a
前一个 Action 可以“解包”为非单子(monad)a
在 >>=
.然后函数 (a -> m b)
需要a
作为它的输入并返回另一个单子(monad)m b
作为其结果。通过绑定(bind)函数,我可以对 monadic 进行操作,而不会给纯 Haskell 代码带来任何副作用。我的问题是为什么我们使用
(a -> m b)
功能?在我看来,m a -> m b
函数也可以做到这一点。有什么原因,还是仅仅因为它是这样设计的?编辑
从评论中我知道很难提取
a
来自 m a
.但是,我想我可以考虑一个单子(monad) m a
作为 a
有副作用。是否可以假设函数
m a -> m b
与 a -> b
类似的行为, 所以我们可以定义 m a -> m b
比如定义a -> b
?
最佳答案
编辑2:好的,这是我从一开始就应该说的:
Monad 是 EDSL,
E 与嵌入式领域特定语言一样。嵌入意味着语言的陈述是我们的语言 Haskell 中的普通值。
让我们尝试让我们拥有一种 IO 语言。想象一下,我们有 print1 :: IO ()
原语,描述了在提示符处打印整数 1
的 Action 。想象一下我们也有 print2 :: IO ()
。两者都是普通的 Haskell 值。在 Haskell 中,我们谈到了这些 Action 。这种 IO 语言仍然需要稍后在“运行”时由运行时系统的某些部分解释/执行。拥有两种语言,我们就有两个世界,两条时间线。
我们可以编写 do { print1 ; print2 }
来描述复合 Action 。但是我们不能创建一个新的原语来在提示符下打印 3
,因为它在我们纯粹的 Haskell 世界之外。我们这里有一个 EDSL,但显然不是一个非常强大的。我们这里必须有无限的原语供应;不是一个成功的提议。它甚至不是 Functor,因为我们无法修改这些值。
现在,如果我们可以呢?然后我们就可以告诉 do { print1 ; print2 ; fmap (1+) print2 }
也打印出 3
。现在它是一个仿函数。更强大,仍然不够灵活。
我们可以灵活地使用构建这些 Action 描述符的原语(如 print1
)。例如print :: Show a => a -> IO a
。我们现在可以讨论更通用的操作,例如 do { print 42; getLine ; putStrLn ("Hello, " ++ "... you!") }
。
但现在我们看到有必要引用以前行动的“结果”。我们希望能够编写 do { print 42; s <- getLine ; putStrLn ("Hello, " ++ s ++ "!") }
。我们想要创建(在 Haskell 世界中)新的 Action 描述(在 Haskell 世界中描述 Action 的 Haskell 值)基于这些 IO Action 将产生的先前 IO Action 的结果(在 Haskell 世界中),当它们运行时,当IO 语言被解释(它描述的 Action 在 IO 世界中执行)。
这意味着能够从 Haskell 值创建这些 IO 语言语句,例如使用 print :: a -> IO a
。这正是您要询问的类型,这就是使 EDSL 成为 Monad 的原因。
想象一下,我们有一个 IO 原语 (a_primitive :: IO Int -> IO ()
),它按原样打印任何正整数,并在打印任何非正整数之前在单独的行上打印 "---"
。然后我们可以按照您的建议编写 a_primitive (return 1)
。
但是 IO 是关闭的;它是不纯的;我们不能在 Haskell 中编写新的 IO 原语,并且不可能为我们脑海中可能出现的每一个新想法都定义了一个原语。所以我们改写 (\x -> if x > 0 then print x else do { putStrln "---"; print x })
,那个 lambda 表达式的类型是 Int -> IO ()
(或多或少)。
如果上述 lambda 表达式中的参数 x
是 IO Int
类型,则表达式 x > 0
将输入错误。如果不使用标准的 a
运算符(或其等效运算符),就无法从 IO a
中获取该 >>=
。
也可以看看:
而且,这个 quote :
"Someone at some point noticed, "oh, in order to get impure effects from pure code I need to do metaprogramming, which means one of my types needs to be 'programs which compute an X'. I want to take a 'program that computes an X' and a function which takes an X and produces the next program, a 'program that computes a Y', and somehow glue them together into a 'program which computes a Y' " (which is the
bind
operation). The IO monad was born."
编辑:这些是四种类型的通用功能应用程序:
( $ ) :: (a -> b) -> a -> b -- plain
(<$>) :: Functor f => (a -> b) -> f a -> f b -- functorial
(<*>) :: Applicative f => f (a -> b) -> f a -> f b -- applicative
(=<<) :: Monad f => (a -> f b) -> f a -> f b -- monadic
这里是相应的类型推导规则,为了清楚起见,颠倒了参数顺序,
a f a f a f a
a -> b a -> b f (a -> b) a -> f b
------ -------- ---------- ----------
b f b f b f b
no `f`s one `f` two `f`s, two `f`s:
both known one known,
one constructed
为什么?他们只是。你的问题是真的,为什么我们需要 Monads?为什么 Functor 或 Applicative Functor 还不够?这肯定已经被多次询问和回答(例如,上面列表中的第二个链接)。一方面,正如我在上面试图展示的,monad 让我们在 Haskell 中编写新的计算。
关于haskell - 为什么haskell的bind函数要把一个函数从非monadic变成monadic,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44117197/