首先,我认为您在理解 Haskell 方面遇到了一些基本问题,所以让我们一步一步地构建这个。希望你会发现这很有帮助。其中一些只会到达您拥有的代码,而另一些则不会,但它是我在编写此代码时所考虑的慢版本。之后,我将尝试回答您的一个特定问题。
我不太确定你想让你的程序做什么。我了解您需要一个程序,该程序将包含人员及其投资列表的文件作为输入读取。但是,我不确定你想用它做什么。您似乎 (a) 想要一个合理的数据结构 ([(String,Integer)]),但随后 (b) 只使用整数,所以我假设您也想对字符串做一些事情。让我们来看看这个。首先,您需要一个函数,该函数可以在给定整数列表的情况下返回最大值。你称之为maximuminvest,但这个功能比投资更通用,那为什么不叫它maximum呢?事实证明,这个功能已经存在。你怎么会知道这个?我推荐Hoogle——它是一个Haskell 搜索引擎,可让您搜索函数名称和类型。你想要一个从整数列表到单个整数的函数,所以让我们search for that。事实证明,第一个结果是maximum,这是您想要的更通用的版本。但是出于学习目的,假设您想自己编写它;在这种情况下,你的实现就好了。
好的,现在我们可以计算最大值。但首先,我们需要构建我们的列表。我们将需要一个[String] -> [(String,Integer)] 类型的函数来将我们的无格式列表转换为合理的列表。好吧,要从字符串中获取整数,我们需要使用read。长话短说,您当前的实现也很好,尽管我会(a)为单项列表添加一个 error 案例(或者,如果我感觉不错,让它返回一个空列表以忽略奇数长度列表的最后一项),以及 (b) 使用带有大写字母的名称,因此我可以区分这些单词(并且可能是不同的名称):
tupledInvestors :: [String] -> [(String, Integer)]
tupledInvestors [] = []
tupledInvestors [_] = error "tupledInvestors: Odd-length list"
tupledInvestors (name:amt:rest) = (name, read amt) : tupledInvestors rest
现在我们有了这些,我们可以为自己提供一个方便的功能,maxInvestment :: [String] -> Integer。唯一缺少的是从元组列表到整数列表的能力。有几种方法可以解决这个问题。一个是你拥有的,尽管这在 Haskell 中是不寻常的。第二个是使用map :: (a -> b) -> [a] -> [b]。这是一个将函数应用于列表的每个元素的函数。因此,您的getint 等同于更简单的map snd。最好的方法可能是使用Data.List.maximumBy :: :: (a -> a -> Ordering) -> [a] -> a。这就像maximum,但它允许您使用自己的比较功能。使用Data.Ord.comparing :: Ord a => (b -> a) -> b -> b -> Ordering,事情变得很好。此功能允许您通过将两个任意对象转换为可以比较的对象来比较它们。因此,我会写
maxInvestment :: [String] -> Integer
maxInvestment = maximumBy (comparing snd) . tupledInvestors
虽然你也可以写maxInvestment = maximum . map snd . tupledInvestors。
好的,现在进入 IO。然后,您的主要功能想要从特定文件中读取,计算最大投资并将其打印出来。一种表示方式是一系列三个不同的步骤:
main :: IO ()
main = do dataStr <- readFile "C:\\Invest.txt"
let maxInv = maxInvestment $ words dataStr
print maxInv
($ 运算符,如果你没见过,它只是函数应用程序,但优先级更方便;它的类型为 (a -> b) -> a -> b,这应该是有道理的。)但是 let maxInv 似乎毫无意义,所以我们可以摆脱它:
main :: IO ()
main = do dataStr <- readFile "C:\\Invest.txt"
print . maxInvestment $ words dataStr
.,如果你还没有看到的话,是函数组合; f . g 与 \x -> f (g x) 相同。 (它的类型为(b -> c) -> (a -> b) -> a -> c,经过一番思考,这应该是有意义的。)因此,f . g $ h x 与f (g (h x)) 相同,只是更易于阅读。
现在,我们能够摆脱let。 <- 呢?为此,我们可以使用=<< :: Monad m => (a -> m b) -> m a -> m b 运算符。请注意,它几乎就像$,但m 几乎污染了所有内容。这允许我们获取一个单子值(这里是readFile "C:\\Invest.txt" :: IO String),将它传递给一个将普通值转换为单子值的函数,然后获取该单子值。因此,我们有
main :: IO ()
main = print . maxInvestment . words =<< readFile "C:\\Invest.txt"
我希望这应该很清楚,尤其是如果您将 =<< 视为一元 $。
我不确定testfile 发生了什么;如果您编辑您的问题以反映这一点,我会尝试更新我的答案。
还有一件事。你说
我想知道我们如何将输入从 monad IO 传递到另一个函数以进行一些计算。
与 Haskell 中的所有内容一样,这是一个类型的问题。所以让我们来看看这里的类型。你有一些函数f :: a -> b 和一些一元值m :: IO a。您想使用f 来获取b 类型的值。这是不可能的,正如我在my answer to your other question 中解释的那样;但是,您可以得到IO b 类型的东西。因此,您需要一个函数来获取您的 f 并为您提供一元版本。换句话说,类型为Monad m => (a -> b) -> (m a -> m b) 的东西。如果我们plug that into Hoogle,第一个结果是Control.Monad.liftM,它恰好具有该类型签名。因此,您可以将liftM 视为与=<< 略有不同的“monadic $”:f `liftM` m 将f 应用于m 的纯结果(根据您使用的任何一个monad)和返回一元结果。区别在于liftM 在左侧采用纯函数,而=<< 采用部分单子函数。
写相同内容的另一种方法是使用do-notation:
do x <- m
return $ f x
这说“从m 中取出x,将f 应用到它,然后将结果提升回monad。”这与声明return . f =<< m 相同,也就是liftM。首先f 执行纯计算;它的结果被传递给return(通过.),它将纯值提升到monad;然后通过=<, 将这个部分单子函数应用到m。
已经很晚了,所以我不确定这有多大意义。让我试着总结一下。简而言之,没有离开 monad 的通用方法。当您想对单子值执行计算时,您将纯值(包括函数)提升到单子中,而 不 反过来;这可能会违反纯度,这将是非常糟糕的™。
我希望实际上回答了你的问题。如果没有,请告诉我,这样我就可以尝试让它更有帮助!