“加入”的直观含义是什么?

时间:2016-06-06 23:53:34

标签: haskell monads

Monad的join的直观含义是什么?

monads-as-containers 类比对我有意义,并且在这些类比join内部是有道理的。值是双重包装的,我们打开一个图层。但众所周知,monad不是一个容器。

在正常情况下,如何使用join编写合理且易于理解的代码,例如何时在IO

5 个答案:

答案 0 :(得分:3)

action :: IO (IO a)是一种产生a的方法。那么,join action是一种通过运行a的最外层生成器生成action的方法,将生成的生成器运行,然后再运行它,最终得到那个多汁的a { {1}}。

答案 1 :(得分:3)

join折叠了类型构造函数的连续图层。

有效的join必须满足以下属性:对于类型构造函数的任意数量的连续应用程序,它不应该与我们折叠图层的顺序相关。

例如

ghci> let lolol = [[['a'],['b','c']],[['d'],['e']]]
ghci> lolol :: [[[Char]]]
ghci> lolol :: [] ([] ([] Char)) -- the type can also be expressed like this

ghci> join (fmap join lolol) -- collapse inner layers first
"abcde"
ghci> join (join lolol) -- collapse outer layers first
"abcde"

(我们使用fmap来"进入"外部monadic层,以便我们可以先折叠内层。)

join有用的小型非容器示例:对于函数monad (->) ajoin等同于\f x -> f x x,类型为(a -> a -> b) -> a -> b的函数将相同参数的两倍应用于另一个函数。

答案 2 :(得分:2)

对于List monad,@pytest.fixture def blue_frob(tmpdir, blueness): return Frob(blueness, workdir=tmpdir) @pytest.fixture def green_frob(greenness, tmpsocket): return Frob(greenness, sock=tmpsocket) all_frobs = [blue_frob, green_frob] frobs = make_fixture_from_fixtures(all_frobs) 只是joinconcatconcatMap。 因此join . fmap隐式出现在使用join的任何列表表达式中 或concat

假设您被要求查找所有任何除数的数字 输入列表中的数字。如果您有concatMap函数:

divisors

你可以解决这个问题:

divisors :: Int -> [Int]
divisors n = [ d | d <- [1..n], mod n d == 0 ]

这里我们正在考虑通过首先映射来解决问题 除数在所有输入元素上起作用,然后连接 所有结果列表。你甚至可能认为这是非常的 &#34;功能&#34;解决问题的方法。

另一个approch是编写列表理解:

foo xs = concat $ (map divisors xs)

或使用do-notation:

bar xs = [ d | x <- xs, d <- divisors x ]

在这里可以说我们正在考虑更多 势在必行 - 首先从列表bar xs = do x <- xs d <- divisors return d 中抽取一个数字;然后画画  从数字的除数中除数并得出它。

但事实证明,xsfoo功能完全相同。

另外,这两种方法在任何 monad中完全相同。 也就是说,对于任何monad,以及适当的monadic函数f和g:

bar

例如,如果我们设置do x <- f y <- g x is the same as: (join . fmap g) f return y f = getLine,则在IO monad中, 我们有:

g = readFile

do-block是表达动作的更为必要的方式:首先阅读a 输入线;然后将返回的字符串视为文件名,读取内容 的文件,最后返回结果。

等效的连接表达式在IO-monad中看起来有点不自然。 但是它不应该像我们一样使用它,就像我们一样 在第一个示例中使用了do x <- getLine y <- readFile x is the same as: (join . fmap readFile) getLine return y

答案 3 :(得分:1)

如果某个操作产生另一个操作,请运行该操作,然后运行它产生的操作。

如果您想象某种Parser x monad解析x,那么Parser (Parser x)是一个解析器,它会进行一些解析,然后返回另一个解析器。因此,join会将其展平为只运行两个操作并返回最终Parser x的{​​{1}}。

为什么你首先要有一个x?基本上,因为Parser (Parser x)。如果你有一个解析器,你可以fmap一个改变结果的函数。但是如果你fmap一个函数本身返回一个解析器,你最终得到一个fmap,你可能只想运行这两个动作。 Parser (Parser x)实现“只运行两个动作”。

我喜欢解析示例,因为解析器通常具有join函数。很明显,runParser 不是整数。它可以解析一个整数,之后给它一些输入来解析。我想很多人最终都认为Parser Int只是一个普通的整数,但是这个烦人的IO Int位是你无法摆脱的。 不是。这是一个未执行的I / O操作。它内部没有“整数”整数;在您实际执行 I / O之前,整数不存在。

答案 4 :(得分:1)

通过写出类型并稍微重构它们来揭示这些函数的用途,我发现这些东西更容易理解。

读者monad

因此定义了Reader类型,其join函数的类型如下所示:

newtype Reader r a = Reader { runReader :: r -> a }

join :: Reader r (Reader r a) -> Reader r a

由于这是newtype,这意味着类型Reader r a 同构r -> a。所以我们可以重构类型定义给我们这种类型,尽管它不一样,它与恐慌引用真的“相同”:

在与(->) r同构的Reader r monad中,join是函数:

join :: (r -> r -> a) -> r -> a

所以Reader连接是一个带两位函数(r -> r -> a)的函数,并且在它的两个参数位置都应用相同的值。

作家monad

由于Writer类型具有此定义:

newtype Writer w a = Writer { runWriter :: (a, w) }

...然后当我们删除newtype时,其join函数的类型为isomorphic:

join :: Monoid w => ((a, w), w) -> (a, w)

Monoid约束需要存在,因为Monad的{​​{1}}实例需要它,它让我们马上猜测函数的作用:

Writer

州monad

同样,由于join ((a, w0), w1) = (a, w0 <> w1) 有这个定义:

State

...然后它的newtype State s a = State { runState :: s -> (a, s) } 是这样的:

join

......你也可以冒险直接写作:

join :: (s -> (s -> (a, s), s)) -> s -> (a, s)

如果你稍微盯着这种类型,你可能会认为它与{em>两者 join f s0 = (a, s2) where (g, s1) = f s0 (a, s2) = g s1 {- Here's the "map" to the variable names in the function: f g s2 s1 s0 s2 join :: (s -> (s -> (a, s ), s )) -> s -> (a, s ) -} Reader的{​​{1}}类型有一些相似之处操作。你是对的! WriterjoinReader monad都是名为update monads的更一般模式的实例。

列出monad

Writer

正如其他人所指出的,这是State函数的类型。

解析monads

这是一个非常巧妙的事情。通常情况下,“花哨”的monad会成为join :: [[a]] -> [a] concatReader或列表等“基本”的组合或变体。因此,当我遇到一个小说的monad时,我常常会问:哪个基本monad与它相似,以及如何?

以解析monad为例,这里已经提到了其他答案。一个简单的解析器monad(不支持错误报告等重要的事情)看起来像这样:

Writer

State是一个以字符串作为输入的函数,并返回候选解析的列表,其中每个候选解析都是一对:

  1. 类型newtype Parser a = Parser { runParser :: String -> [(a, String)] } ;
  2. 的解析结果
  3. 剩余部分(该解析中未使用的输入字符串的后缀)。
  4. 但请注意,这种类型看起来非常像州monad:

    Parser

    这不是偶然的!解析器monad是非确定状态monad ,其中状态是输入字符串的未消耗部分,解析步骤生成替代,可能随后根据进一步输入被拒绝。列表monad通常被称为“nondeterminism”monad,因此解析器类似于state和list monad的混合也就不足为奇了。

    使用 monad transfomers 可以使这种直觉系统化。状态monad变换器的定义如下:

    a

    这意味着上面的newtype Parser a = Parser { runParser :: String -> [(a, String)] } newtype State s a = State { runState :: s -> (a, s) } 类型也可以这样写:

    newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }
    

    ...其Parser个实例跟随机械来自type Parser a = StateT String [] a Monad

    StateT monad

    想象一下,我们可以枚举所有可能的原始 []操作,有点像这样:

    IO

    然后我们可以将IO类型视为此(我从高度推荐的Operational monad tutorial改编而来):

    {-# LANGUAGE GADTs #-}
    
    data Command a where
      -- An action that writes a char to stdout
      putChar :: Char -> Command ()
    
      -- An action that reads a char from stdin
      getChar :: Command Char
    
      -- ...
    

    然后IO行动将如下所示:

    data IO a where
      -- An `IO` action that just returns a constant value.
      Return :: a -> IO a
    
      -- An action that binds the result of a `Command` to 
      -- a function that computes the next step after it.
      Bind :: Command x -> (x -> IO a) -> IO a
    
    instance Monad IO where ...
    

    因此join所做的一切都是“追逐”join :: IO (IO a) -> IO a -- If the action is just `Return`, then its payload already -- is what we need to return. join (Return ioa) = ioa -- If the action is a `Bind`, then its "next step" function -- `f` produces `IO (IO a)`, so we can just recursively stick -- a `join` to its result end. join (Bind cmd f) = Bind cmd (join . f) 操作,直到它看到符合模式join的结果,然后删除外部IO }。

    那我在这做什么?就像解析器monad一样,我只是定义(或者更确切地说是)具有透明优点的Return (ma :: IO a)类型的玩具模型。然后我从玩具模型中找出Return的行为。

相关问题