【问题标题】:IO in where clausewhere 子句中的 IO
【发布时间】:2011-07-24 23:37:27
【问题描述】:

我以为我开始了解 Haskell 中的 IO,直到遇到以下问题。

我有以下函数,它返回类型 IO Float:

getFundPrice :: Int ->  Int -> IO Float
getFundPrice fund date = do 
                       priceList <- getFundPrice' fund date
                       let h = head priceList
                       return  h

函数 getFundPrice' 使用 takusen 数据库并返回一个 IO [Float] 类型的列表。

我能够使用 Hunit 成功测试 getFundPrice 函数,使用以下方法:

  p <- getFundPrice 120 20100303
  assertEqual
    "get fund price"
    10.286 
    (p)

困扰我的问题是以下函数定义:

lastPos :: Int -> (Shares,Float) -> Period -> Fund -> (Shares,Float)
lastPos endDate begPos [] fund  = (fst begPos, trnPrice) 
                               where trnPrice = do
                                             price <- getFundPrice fund endDate
                                             return price                                                                        

我在尝试编译时遇到的错误是“无法匹配预期的类型Float' against inferred typeIO Float'”

我认为 *price 操作会像处理我的 HUnit 代码一样为我检索价格。

在 where 子句中使用 this 有什么不同?

【问题讨论】:

  • 确保你清楚地了解 return 在 Haskell 中的作用。

标签: haskell io monads


【解决方案1】:

如果您删除显式类型签名,您将看到正确的类型:

lastPos :: Int -> (Shares,Float) -> Period -> Fund -> (Shares,IO Float)

注意最后的IO Float。您已将 trnPrice 定义为检索浮点值的 IO 操作。您没有执行该操作来检索值!除了lastPos 不在的 IO monad 之外,您无法从任何地方执行该操作。您可以做的是:

lastPos :: Int -> (Shares,Float) -> Period -> Fund -> IO (Shares, Float)
lastPos endDate begPos [] fund  = do
    price <- getFundPrice fund endDate
    return (fst begPos, price)

这会将所有 lastPos 提升到 IO 中。

【讨论】:

  • 谢谢,这很有帮助...根据我对另一个答案的评论,我正在构建的应用程序有很多业务逻辑功能,这些功能可能是纯的,但这些功能操作的核心数据全部来自数据库,这将导致到处都是 IO。似乎我将失去业务逻辑功能中纯度的好处。有没有更好的策略来处理这样的应用程序?
  • @Neil,一个常见的误解是,如果您的程序执行大量 IO,那么您的程序必须在任何地方都有 IO。这只是意味着您必须将其 IO 设计为在外部。这通常很容易做到——它使您以数学方式而不是操作方式对对象进行建模。在您的示例中,只需让您的业务逻辑函数将核心数据作为参数(它们确实依赖核心数据,对吗?——这使它们起作用)。
  • @Neil,或者如果您的意思是业务逻辑基本上被表述为数据库查询,那么我敢肯定有人已经对 SQL 的某些子集进行了指称建模,因此它们可以是纯粹的 一些只能在IO 中“评估”的抽象。例如。 Database Int 表示一个 int,其值在某种程度上取决于数据库的状态。检查此类模型的黑客攻击。
  • 感谢@Luqui,在这种情况下我面临的挑战是lastPos 函数还有五个其他定义,但其中只有一个需要查询数据库并处理IO。我可以通过某种方式返回一条信息丢失的指示(我们需要进行数据库查询),但这似乎有点令人费解。无论如何,我会弄清楚的。
【解决方案2】:

IO monad 的特点是你永远无法摆脱不纯的污点。因为你的 getFundPrice 函数不是纯函数,所以调用它的也不是纯函数。

你的函数必须有类型

lastPos :: Int -> (Shares,Float) -> Period -> Fund -> IO (Shares,Float)

如果有助于澄清问题,则在您的 where 子句中,return 语句将纯价格值包装在 IO 中。没有办法1避免这样做 - IO monad 被设计为不允许这样做。

其实和where trnPrice = getFundPrice fund endDate一模一样

1其实有一个backdoor,但是用它不是个好主意

【讨论】:

  • 谢谢,这很有帮助。我正在构建的应用程序有很多业务逻辑功能,可以是纯的,但是这些功能所操作的核心数据都来自数据库,这将导致到处都是 IO。似乎我将失去业务逻辑功能中纯度的好处。有没有更好的策略来处理这样的应用程序?
  • 您应该尝试将代码的纯部分与不纯部分分开。当你弄清楚如何最好地分离代码时,通常会得到更好的设计。
  • 好吧,你仍然可以将数据传递给纯业务逻辑函数,只要你在另一个 IO 函数中进行。您的业​​务逻辑仍然可以是纯粹的(应该如此)。
  • 我不介意对 unsafePerformIO 的引用,但在寻找好的答案时,我想要一个链接,说明为什么这(恕我直言)是一个坏主意(而不仅仅是一个坏主意);) .人们可能会觉得使用后门是解决这个特定问题的一种常见做法。
  • @Jonke 我认为 unsafePerformIO wiki 文档应该足以吓跑人们 - 它使用诸如“不确定”和“核心转储”之类的词 =O
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-04-04
  • 1970-01-01
  • 2014-09-08
  • 2013-03-02
  • 1970-01-01
  • 2019-03-22
  • 2020-04-04
相关资源
最近更新 更多