Haskell 有条件地附加到列表

Haskell append to a list conditionally

我有 2 个列表,我正在尝试填写遗嘱项目。从 stdin 读取时,根据读取的内容之一的值,我想附加到不同的列表。例如,

import Control.Monad(replicateM)

main = do
    n <- getLine
    let l1 = [], l2 = []
        in replicateM (read n) (getLine >>= (\line ->
            case line of "Yes" ->
                -- do something with line
                -- and append value of that thing to l1
                          "No" ->
                -- do something else
                -- append this value to l2
            putStrLn line))

我知道上面的代码有语法错误等,但希望你能看到我正在尝试的东西并提出一些建议。

这是我想出的答案

当我们在做的时候,有人可以解释为什么这给了我一个无限的列表:

let g = []
let g = 1:g
-- g now contains an infinite list of 1's

这是我最终想出的:

import Control.Monad(replicateM)
import Data.Either

getEither::[String] -> [Either Double Double]
getEither [] = []
getEither (line:rest) = let [n, h] = words line
                            fn = read f :: Double
                            e = case heist of "Yes" -> Left fn
                                              "No"  -> Right fn
                            in e : getEither rest

main = do
    n <- getLine
    lines <- replicateM (read n) getLine
    let tup = partitionEithers $ getEither lines :: ([Double], [Double])
    print tup

不确定fmap如何在这种情况下使用

这是一个简短的 ghci 会话,可能会给您一些想法:

> :m + Control.Monad Data.Either
> partitionEithers <$> replicateM 3 readLn :: IO ([Int], [Bool])
Left 5
Right True
Left 7
([5,7],[True])

你第二个问题的答案是let是递归的;所以 let g = 1:g 中的两个 g 指的是同一个内存中对象。

您正在考虑可变变量:您 "initializing" l1,l2 到空列表,然后推理用更长的列表更新它们。这种设计在命令式编程中工作得很好,但在纯函数式编程中就不那么简单了,因为它涉及到变异。

现在,即使在纯函数式编程中,我们也有办法通过 monad 来模拟突变。比如这里曾经可以通过IORefs或者StateT IO实现变异。但是,在这种情况下,这将是一种不必要的复杂方法来解决任务。

您想附加数据以形成两个列表。你想使用 replicateM,这很好。关键是 replicateM 将只构建一个列表,而不是两个。现在的问题是:我们怎样才能创建一个很容易分成两部分的列表?

第一个丑陋的尝试是生成标记值列表,即成对列表:

case line of
   "Yes" -> let value = ... in
            return ("for l1", value)
   "No"  -> let value = ... in
            return ("for l2", value)

这样做会使 replicateM 产生一个列表,例如

[("for l1", value1), ("for l1", value2), ("for l2", value3), ...]

然后我们可以将其分成两个列表。

使用字符串作为标签看起来有点不雅,因为布尔值就足够了:

case line of
   "Yes" -> let value = ... in
            return (True, value)
   "No"  -> let value = ... in
            return (False, value)

更好的方法是使用 Either a b 类型:

case line of
   "Yes" -> let value1 = ... in
            return (Left value1)
   "No"  -> let value2 = ... in
            return (Right value2)

上面的好处是 value1value2 甚至可以是不同的类型。前面的片段强制他们共享他们的类型:因为我们构建了一个对列表,每对必须具有相同的类型。新列表现在不是 [Either a b] 类型,其中 a 是要放入 l1 的值类型,bl2.

一旦你得到 [Either a b],你想将它分成 [a][b]。正如@DanielWagner 在他的回答中建议的那样,您可以为此利用 partitionEithers