【问题标题】:Print list elements in new lines在新行中打印列表元素
【发布时间】:2012-09-09 19:45:50
【问题描述】:

我对列表和单子完全感到困惑,所以也许我的问题不正确或非常幼稚。 我已经看到了使用 mapM_ func here 的方法:

mapM_ print [1, 2, 3, 4]

但我不知道它是如何工作的,我想知道如何以这样的方式做到这一点:

x <- [1, 2, 3]
print x

或者,如果我理解正确的话:

[1, 2, 3] >>= print

我知道 [1, 2, 3] 的类型为 [a],而 print 的类型为 Show a =&gt; a -&gt; IO ()。另外我知道,要使用 monad List,我们需要在左侧输入 List a 并在右侧使用 a -&gt; List b 类型的函数。我对吗? 你能帮我解决这个问题吗?

UPD。感谢@MathematicalOrchid 解释 mapM_ 的工作原理。从我的角度来看,我想解释真正的问题不是在不同的行中打印任何结果,而是以 monad List 提供的方式执行一些 monadic 操作(因为现在我在 OpenGL 周围徘徊)。但我知道误解的根源在于混合单子。

UPD2。谢谢大家的回答。我为这个模糊的问题道歉。我不完全知道我需要什么答案以及问题是什么。那是因为我不了解一些基础知识。所以现在很难选择“正确的答案”,因为每个答案都与我正在寻找的东西有点和平。我决定选择最接近(虽然现在不是最有用的)我想要的东西。

【问题讨论】:

  • forM_ 可能看起来更熟悉一些。它只是颠倒了mapM_ 的论点。试试forM_ [1, 2, 3] print
  • 值得一提的是,您使用了两个 monad:ListIO。您的&gt;&gt;= 不起作用的原因是您试图将两者混合使用。
  • @stusmith:是的,我也明白了 :)

标签: list haskell monads monad-transformers


【解决方案1】:

您似乎在这里混淆了几件事。 (特别是,列表形成了一个 monad,而 I/O 形成了一个不同的 monad。)我将尝试澄清这一点......

首先,print 函数获取任何可显示的内容并将其写入标准输出,然后是换行符。所以print [1, 2, 3] 工作得很好,但显然将所有内容都写在同一行。要在单独的行上写东西,我们需要为每个项目单独调用print。到目前为止,一切顺利。

map 函数将函数应用于列表的每个元素。所以map print [1, 2, 3] 会将print 应用于列表中的每个项目。 然而,结果是一个 I/O 操作列表。这不是我们所追求的。我们希望执行这些操作,而不是列出它们。

执行此操作的方法是使用&gt;&gt; 运算符,它将两个 I/O 操作链接在一起(前提是您对它们的结果不感兴趣 - 并且打印某些内容不会返回任何有趣的内容)。因此foldr (&gt;&gt;) (return ()) 将获取您的 I/O 操作列表并将其转换为单个 I/O 操作。这个函数实际上已经定义好了;它被称为sequence

然而,map + sequence 是一个很常见的组合,this 也已经被定义了;它被称为mapM_。 (还有mapM,如果你想保留结果,不带下划线。但打印不返回任何内容,所以没有必要。)


现在,这就是 mapM_ 起作用的原因。现在你问为什么其他几种方法都行不通...

x <- [1, 2, 3]
print x

这根本不起作用。第一行在列表 monad 中。但第二行在 I/O monad 中。你不能那样做。 (你会得到一个相当莫名其妙的类型检查器错误。)我应该指出这是 Haskell 所谓的“do-notation”,上面的片段需要前面的 do 关键字才能真正成为有效的语法:

do
  x <- [1, 2, 3]
  print x

无论如何,它仍然不起作用。它几乎做了map print [1, 2, 3] 所做的事情,但并不完全如此。 (正如我所说,它不会进行类型检查。)

您还建议了[1, 2, 3] &gt;&gt;= print,这与之前的sn-p相同。 (事实上​​,编译器将前者转换为后者。)原版不进行类型检查,而本版也不进行类型检查,原因相同。

这有点像尝试将数字添加到矩阵中。数字是可加的东西。矩阵是可添加的东西。但是您不能将一个添加到另一个,因为它们不一样。如果这有意义的话。

【讨论】:

  • 解释得很清楚!但请再问一个问题。有没有办法混合单子(从一个跳到另一个)?现在我看到至少有两种方法可以“独立”地做一些动作——通过映射映射函数(或其单子反射)或将动作放入 List 单子中。我说的对吗?
  • @pkuderov 对于某些特定的单子对mn,有一些方法可以从m 跳转到n,但列表和IO 不是这样的一对。相反,通常的技术是创建一个新的 monad,它具有您想要在使用 monad 转换器之间跳转的两个 monad 的特征。没有 monad 转换器可以让你添加IO 效果类似列表的东西,但是有is a transformer 允许你向IO 添加不确定性(即类似列表的特征)。
【解决方案2】:

你想要的东西不能这样工作,因为你试图将两个 monad 混合在一起:

do x <- [1,2,3]
   print x

具体来说,您正在混合 IO[] 单子。在 do-notation 中,对于某些 Monad m,所有语句都应该具有 m a 类型。但是在上面的代码中,第一个语句的类型是[Integer],而第二个语句的类型是IO ()

要获得您想要的效果,您应该使用ListT monad 转换器。 Monad transformers 允许在堆栈中以特定顺序将 monad 混合在一起,并根据需要组合它们的效果。

import Control.Monad.Trans
import Control.Monad.Trans.List

value = do x <- ListT (return [1,2,3])
           lift (print x)

这将返回一个ListT IO Integer 类型的值。要从此转换器中获取IO 计算,请使用runListT。这将返回一个IO [Integer] 类型的值。这将输出:

GHCI> runListT value
1
2
3
[(),(),()]

相当于mapM print [1,2,3]。要丢弃列表并获得mapM_ print [1,2,3] 的效果,您可以使用Control.Monad 中的void

GHCI> void . runListT $ value
1
2
3

【讨论】:

  • 感谢 ListT。我现在不打算跳入 monad 转换器 - 它的时间还没有成为我。但这正是我一直在寻找的东西之一。
  • @pkuderov 很高兴为您提供帮助。我知道它现在有点先进。但我认为我应该添加它,因为它通常可以解决您要解决的问题。
【解决方案3】:

你可以使用sequence_依次执行IO动作:

sequence_ $ [1, 2, 3] >>= (\x -> [print x])

但我认为mapM_ 更清晰。

【讨论】:

  • 只是不想让任何人认为这是一个明智的建议。尽管sequence 可以方便地从操作列表中创建单个操作。
  • 还有,sequence_ $ map print [1,2,3]
【解决方案4】:

我不会准确回答你的问题,因为我认为这个问题本身有点误导。特别是,在这里使用mapM 或类似的东西是正确的做法。对这个任务使用 do 符号只会让它变得更加复杂,而且我不愿意告诉人们不应该做的事情正确的事情。但我会为您提供一个您可能会觉得更容易理解的替代方案。

如果您来自命令式背景(即您熟悉诸如 C、Java、Python 之类的东西...),那么您可能会发现使用forM 比使用mapM 更容易。语法是

forM <list of things> <action to perform for each thing>

即这就像一个 for-each 循环!例如:

ghci> import Control.Monad
ghci> forM [1,2,3] print
1
2
3
[(),(),()]

事物列表是[1,2,3],对每个事物执行的操作是print。注意到最后的返回值了吗?那是因为对print 的每次调用都会返回(),并且在最后将它们收集在一起。如果您不想要返回值,请使用 forM_ 而不是 forM,如下所示:

ghci> forM_ [1,2,3] print
1
2
3

你准备好接受秘密了吗?函数forMforM_ 只是mapMmapM_ 的参数相反,即:

forM list action = mapM action list

我经常在我的代码中使用forM,因为它会引起人们对函数而不是list的关注,而这正是你想要的。当 then 函数跨越多行时,它看起来也更整洁。

【讨论】:

    【解决方案5】:

    这可能是对mapM_ 工作原理的最简单解释:

    main = foldr1 (>>) (map print [1, 2, 3])
    

    print应用于每个列表成员,结果使用&gt;&gt;连接,所以首先你得到

    main = foldr1 (>>) [print 1, print 2, print 3]
    

    最后你得到了

    main = print 1 >> print 2 >> print 3 
    

    更准确的解释是这样的:

    main = foldr (>>) (return ()) (map print [1, 2, 3])
    

    所以最后你得到了

    main = print 1 >> print 2 >> print 3 >> return ()
    

    return () 部分让函数可以处理空列表 - foldr1 只是在空列表上崩溃,就像 headtail 一样。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-10-12
      • 1970-01-01
      • 2011-10-11
      • 1970-01-01
      • 2020-06-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多