首先,我认为你在理解 Haskell 方面遇到了一些基本问题,所以让我们一步一步地构建这个。希望你会发现这很有帮助。其中一些只会到达您拥有的代码,而另一些则不会,但它是我在编写此代码时所考虑的慢版本。之后,我将尝试回答您的一个特定问题。
我不太确定你想让你的程序做什么。我了解您需要一个程序,该程序将包含人员及其投资列表的文件作为输入读取。但是,我不确定你想用它做什么。您似乎 (a) 想要一个合理的数据结构 ( [(String,Integer)]
),但随后 (b) 只使用整数,所以我假设您也想对字符串做一些事情。让我们来看看这个。首先,您需要一个函数,该函数可以在给定整数列表的情况下返回最大值。你叫这个maximuminvest
,但是这个函数比投资更通用,那为什么不叫呢maximum
?事实证明,这个功能已经存在。你怎么会知道这个?我推荐Hoogle——它是一个 Haskell 搜索引擎,可让您搜索函数名称和类型。你想要一个从整数列表到单个整数的函数,所以让我们搜索那个. 事实证明,第一个结果是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]
. 这是一个将函数应用于列表的每个元素的函数。因此, yourgetint
相当于更简单的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
($
操作符,如果你没见过的话,它只是函数应用程序,但优先级更方便;它有 type (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)
。(它有 type (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"
我希望这应该很清楚,特别是如果您认为=<<
是 monadic $
。
我不确定发生了什么testfile
;如果您编辑您的问题以反映这一点,我会尝试更新我的答案。
还有一件事。你说
我想知道我们如何将输入从 monad IO 传递到另一个函数以进行一些计算。
与 Haskell 中的所有内容一样,这是类型的问题。所以让我们来看看这里的类型。你有一些功能f :: a -> b
和一些单子价值m :: IO a
。您想用来f
获取类型的值b
。正如我在回答您的另一个问题时所解释的那样,这是不可能的;但是,您可以获得type 的东西IO b
。因此,您需要一个函数来获取f
并为您提供一元版本。换句话说,带有 type 的东西Monad m => (a -> b) -> (m a -> m b)
。如果我们将它插入到 Hoogle中,第一个结果是Control.Monad.liftM
,它恰好具有该类型签名。因此,您可以将liftM
其视为与以下稍有不同的“单子$
” =<<
:f `liftM` m
适用于(根据您使用的任何单子)f
的纯结果并返回单子结果。m
不同之处在于liftM
左侧采用纯函数,而=<<
采用部分单子函数。
编写相同内容的另一种方法是使用do
-notation:
do x <- m
return $ f x
这就是说“x
退出m
,应用f
它,然后将结果提升回单子。” 这与语句相同return . f =<< m
,也正是如此liftM
。首先f
执行纯计算;它的结果被传递给return
(via .
),它将纯值提升到 monad;然后通过=<,
, 将这个部分单子函数应用到m
.
已经很晚了,所以我不确定这有多大意义。让我试着总结一下。简而言之,没有离开 monad 的通用方法。当您想对单子值执行计算时,您将纯值(包括函数)提升到单子中,而不是相反;这可能会违反纯度,这将是非常糟糕的™。
我希望这实际上回答了你的问题。如果没有,请告诉我,所以我可以尝试使它更有帮助!