如何在Haskell中“中断”IO操作

3
我希望在Haskell中拥有“return”(命令式语言中的)函数。
例如:
main = do
  let a = 10
  print a
--  return this function
  print $ a + 1

我该如何实现这个目标?

1
你想要一个“提前退出”的行为吗? - bheklilr
1
你不能仅使用 IO 来完成这个任务。你可以将其与 ErrorTContT 结合使用来实现。或者你可以使用 Exception - Rufflewind
@bheklilr 是的。有时候我调试程序的时候需要它。 - worldterminator
@worldterminator 就像 Rufflewind 所说的那样,这通常无法使用IO实现,但有些Monad可以。具体而言,请查看 MonadPlus 类型类,定义了提前退出的行为。正如您所见,IO不是实现此类型类的其中之一。您可以查看 MonadError 类型类,它来自 mtl 库的另一个实现,定义了引发错误的行为,并提供了几个实例。 - bheklilr
2
请注意,如果您想在调试时中断执行,GHCi具有内置调试器,可设置断点。 - Ørjan Johansen
2个回答

2
你可以使用Exception进行某种程度的模拟。
{-# LANGUAGE DeriveDataTypeable #-}
import Control.Exception
import Data.Typeable

data MyException = MyException deriving (Show, Typeable)
instance Exception MyException

main = handle (\ MyException -> return ()) $ do
  let a = 10 :: Int
  print a
  throwIO MyException
  print $ a + 1         -- never gets executed

你也可以使用 ContTErrorT 单子变换器 来完成,尽管它们可能有些笨重。

1
虽然您确实可以抛出异常来改变程序的流程,但使用它们来塑造其正确行为是不良设计。异常是用于处理异常行为的。如果您需要这样做,则应重构函数。要具有功能性而非命令式。 - mariop
@mariop:我认为是你从命令式的角度看待异常。Haskell中的“异常”只是带有一些附加语义的Either e单子,而单子是建模控制流的绝佳选择。虽然我同意你的观点,即OP试图在Haskell中编写命令式代码,就好像它是<通用命令式语言>,并且在这种情况下使用异常可能是错误的。 - cdk
@mariop:我并不是在推崇使用这种方法,只是在展示它可以被实现。是否是一个好的实践方式则是另外一个问题。 - Rufflewind
1
@cdk 我不同意将 Either e 与异常进行比较。Either e 是显式的,而异常是隐式的,这是一个巨大的区别。对于错误和正常行为,请使用 Either,对于异常行为,请使用异常。 - mariop
@Rufflewind 好的,但即使正确,我仍然认为这不是一个好答案。您展示的是一些不应该做的事情,而且有原因。在我看来,最好解释一下为什么Haskell中没有“命令式返回”,以及为什么这是好的。 - mariop
@mariop:我没有意识到这些是在IO单子中的异步异常,而不是在ErrorTEitherT单子中的显式同步异常。我在阅读Rufflewind的回答时有点匆忙。在这种情况下,当然你是正确的。 - cdk

2

首先,我要警告的是,试图将命令式结构翻译成Haskell可能会导致不符合惯用法、难以编写和难以阅读的代码。仅仅因为您可以使用几个monad转换器模拟一些结构,并不意味着这样做就应该实际操作。

话虽如此,下面是一个使用Control.Monad.Cont.ContT进行早期返回的示例。以下代码在几个for循环中模拟了一个命令式返回。

正如Rufflewind所警告的那样,这可能变得难以处理。仅callCC函数的类型(未在下面显示)就可能会非常令人困惑。

import Control.Monad.Cont

search :: Int -> IO (Maybe (Int,Int))
search x = runContT (callCC go) return
  where go earlyReturn = do
           forM_ [10..50] $ \i -> do
              lift $ putStrLn $ "Trying i=" ++ show i
              forM_ [10..50] $ \j -> do
                lift $ putStrLn $ "Trying j=" ++ show j
                when (i * j == x) $ do
                   lift $ putStrLn $ "Found " ++ show (i,j)
                   earlyReturn $ Just (i,j)
           return Nothing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接