代码之家  ›  专栏  ›  技术社区  ›  StackedCrooked

关于哈斯凯尔单子的一些问题

  •  11
  • StackedCrooked  · 技术社区  · 15 年前

    我在学习单子,还有几个问题。

    我现在就在这里。请纠正我的错误。

    • 这个 >>= 符号是中缀运算符。中缀运算符是接受两个参数(左侧和右侧)并返回值的函数。

    • 这个 >>= 符号称为绑定运算符并具有签名 Monad m => m t -> (t -> m u) -> m u . 然而,这些类型似乎并不在这里。我们得到一个类型的值 m t 第二个参数是一个函数, t . (我不知道如何连接这些点。)

    • 这必须意味着bind函数能够以某种方式删除 m 米T 为了得到 T 并将其传递给函数。

    以下是我的问题:

    • 是否能够移除 米T 只有在这样的绑定操作符中才可能出现的东西。这个绑定操作符是否有一些特殊的特权?

    • 它与状态变化有什么关系?我理解(我认为)Monads的目标是“包装”副作用,使它们与程序的其余部分隔离开来。但是bind操作符在这里扮演什么角色?

    6 回复  |  直到 10 年前
        1
  •  10
  •   Community Jaime Torres    7 年前

    是从“m t”中删除“m”的能力,它只能在这样的绑定运算符内进行。这个绑定操作符是否有一些特殊的特权?

    bind并不是一种特殊情况,但通常它将在与monads数据类型相同的模块中定义。因此,它可能知道(和使用)模块未导出的详细信息。通常情况下,模块会导出一个数据类型,但它不是构造函数或有关类型内部结构的其他详细信息。然后,对于使用模块的代码,数据类型的内部工作是不可见的,并且该代码不能直接修改此类型的值。

    与模块内定义的函数相反,例如一些绑定运算符 >>= ,可以从定义它们的模块中访问它们喜欢的任何内容。所以这样的函数可能能够做“外部”函数不能做的事情。

    特殊情况是 IO monad,因为它不是由模块定义的,而是内置在运行时系统/编译器中。在这里,编译器了解其实现的内部细节,并公开如下函数 输入输出 >>= . 这些函数的实现确实是特殊的特权,因为它们“位于程序之外”,但这是一个特殊的情况,不应该从Haskell内部观察到这一事实。

    它与状态变化有什么关系?我理解(我认为)Monads的目标是“包装”副作用,使它们与程序的其余部分隔离开来。但是bind操作符在这里扮演什么角色?

    它实际上不需要与状态变化有关,这只是一个可以用抱怨来处理的问题。这个 输入输出 monad用于按一定的顺序执行IO,但通常monad只是将函数组合在一起的方法。

    一般来说,monad(特别是bind函数)定义了一种方法,在这种方法中,某些函数应该组合成更大的函数。这种组合函数的方法是在monad中抽象出来的。这种组合到底是如何工作的,或者为什么要以这种方式组合函数并不重要,monad只是指定了以某种方式组合某些函数的方法。(也见) this "Monads for C# programmers" answer 我基本上用例子重复了几次。)

        2
  •  12
  •   Don Stewart    15 年前

    是从“m t”中删除“m”的能力,它只能在这样的绑定运算符内进行。

    当然,在bind运算符内部也是可能的,因为它的类型指定了:

    (>>=) :: m a -> (a -> m b) -> m b
    

    monad的“run”函数通常也可以这样做(从计算中返回一个纯值)。

    Monads的目标是“包装”副作用,使它们与程序的其余部分隔离开来。

    嗯,不,单子让我们模拟计算的概念。副作用计算只是这样一个概念,即状态、回溯、延续、并发性、事务、可选结果、随机结果、可恢复状态、不确定性……所有这些 can be described as a monad

    我想IO Monad就是你所指的。它是一个有点奇怪的monad——它生成对世界状态的抽象更改序列,然后由运行时进行评估。bind只是让我们按照IO monad的正确顺序对事物进行排序——然后编译器会将所有这些顺序化的世界修改操作转换成命令式代码,从而改变机器的状态。

    这是非常具体的IO Monad虽然,不是一般的Monad。

        3
  •  5
  •   yfeldblum    15 年前

    下面是类型类的定义 Monad .

    class  Monad m  where
    
        (>>=)       :: forall a b. m a -> (a -> m b) -> m b
        (>>)        :: forall a b. m a -> m b -> m b
        return      :: a -> m a
        fail        :: String -> m a
    
        m >> k      = m >>= \_ -> k
        fail s      = error s
    

    类型类的每个类型实例 单子 定义自己的 >>= 功能。下面是一个来自类型实例的示例 Maybe :

    instance  Monad Maybe  where
    
        (Just x) >>= k      = k x
        Nothing  >>= _      = Nothing
    
        (Just _) >>  k      = k
        Nothing  >>  _      = Nothing
    
        return              = Just
        fail _              = Nothing
    

    正如我们看到的,因为 也许吧 版本 >>= 是专门为理解 也许吧 类型实例,因为它是在具有对 data Maybe a 数据构造函数 Nothing Just a , the 也许吧 版本 >>= 能够打开 a Maybe a 然后通过。

    为了完成一个例子,我们可以采取:

    x :: Maybe Integer
    x = do a <- Just 5
           b <- Just (a + 1)
           return b
    

    除糖后,do符号变为:

    x :: Maybe Integer
    x = Just 5        >>= \a ->
        Just (a + 1)  >>= \b ->
        Just b
    

    其计算结果为:

      =                  (\a ->
        Just (a + 1)  >>= \b ->
        Just b) 5
    
      = Just (5 + 1)  >>= \b ->
        Just b
    
      =                  (\b ->
        Just b) (5 + 1)
    
      = Just (5 + 1)
    
      = Just 6
    
        4
  •  4
  •   Apocalisp    15 年前

    有趣的是,这些类型确实排成一行。这是怎么回事。

    记住monad也是一个函数。为所有函数定义了以下函数:

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

    现在的问题是:这些类型真的排列起来了吗?嗯,是的。给定的函数来自 a b 如果我们有环境 f 在哪儿 有,我们有一个环境 F 在哪儿 是可用的。

    类比三段论:

    (Functor Socrates) => (Man -> Mortal) -> Socrates Man -> Socrates Mortal
    

    现在,如您所知,monad是一个装备了bind和return的函数:

    return :: (Monad m) => a -> m a
    (=<<) :: (Monad m) => (a -> m b) -> m a -> m b
    

    你可能不知道,它是一个装备了返回和连接的函数:

    join :: (Monad m) => m (m a) -> m a
    

    看看我们是如何剥离 m . 用单子 你不能总是从 m a 但是你总是可以从 m (m a) .

    现在看第一个论点 (=<<) . 这是一个类型的函数 (a -> m b) . 当你把函数传递给 fmap 是吗?你得到 m a -> m (m b) . 所以,“映射”在 具有函数 a -> m b 给你 m (m b) . 请注意,这与 join . 这不是巧合。“bind”的合理实现如下:

    (>>=) :: m a -> (a -> m b) -> m b
    x >>= f = join (fmap f x)
    

    实际上,bind和join可以根据彼此定义:

    join = (>>= id)
    
        5
  •  2
  •   Brandon Browning    15 年前

    我非常推荐你读( http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html )它给出了一个完美的,常识性的单子存在的原因。

        6
  •  2
  •   Paul Johnson    15 年前

    我理解(我认为)Monads的目标是“包装”副作用,使它们与程序的其余部分隔离开来。

    实际上比这更微妙。monads允许我们以一种非常一般的方式对序列进行建模。通常,当你和一个领域专家交谈时,你会发现他们说“首先我们尝试X,然后我们尝试Y,如果这不起作用,那么我们尝试Z”。当你在传统语言中实现类似的东西时,你会发现它不适合,所以你必须编写大量额外的代码来覆盖领域专家所说的“那么”这个词。

    在haskell中,您可以将其作为monad实现,并将“then”转换为bind运算符。例如,我曾经写过一个程序,其中一个项目必须根据特定的规则从池中分配。对于案例1,您是从池X中取出的。如果是空的,那么您就转到池Y中。对于案例2,您必须直接从池Y中取出它。对于大约十几个案例,包括您最近从池X或池Y中取用最少的案例。我专门为该作业编写了一个自定义Monad,以便我可以编写:

    case c of
       1: do {try poolX; try poolY}
       2: try poolY
       3: try $ lru [poolX, poolY]
    

    它工作得很好。

    当然,这包括传统的排序模型。IO Monad是所有其他编程语言所拥有的模型;它只是Haskell中的一个明确选择,而不是环境的一部分。st-monad给出了IO的记忆突变,但是没有实际的输入和输出。另一方面,状态monad允许您将状态限制为命名类型的单个值。

    关于真正的大脑弯曲,请看 this blog post 关于后向状态Monad。状态的传播方向与“执行”相反。如果您将其视为一个状态单元执行一条指令,然后执行下一条指令,那么“put”将及时将状态值向后发送到前面的任何“get”。什么 事实上 发生的情况是建立了一个相互递归的函数,该函数只有在没有悖论的情况下才会终止。我不太确定在哪里使用这样的monad,但它说明了monad是计算模型的要点。

    如果您还没有准备好,那么只需将bind看作一个可重载的分号。这让你走了很长的路。