提问人:rex 提问时间:2/28/2023 更新时间:2/28/2023 访问量:106
有没有办法在 Haskell 中链接纯函数和 IO 函数?
Is there a way to chain pure and IO functions in Haskell?
问:
我目前正在学习Haskell。我在使用纯函数编写一些 IO 函数时遇到了困难(我不确定使用我正在使用的结构是否可以做到这一点)。
我在下面包含了复制我实际问题的完整玩具代码。
import Data.Function ((&))
data MyType = I Int | S String | B Bool deriving (Show)
debugPrint :: Bool -> MyType -> IO (Either String MyType)
debugPrint flag mt =
if flag
then do
print mt
return (Left "Debug mode: short circuiting further steps")
else return (Right mt)
strToInt :: String -> Either String MyType
strToInt s =
if sLen >= 10
then Left "String is too long"
else Right (I sLen)
where
sLen = length s
intToBool :: MyType -> Either String MyType
intToBool (I i) =
if i <= 0
then Left "Integer is <= 0"
else Right (B (i > 5))
intToBool _ = undefined
boolToStr :: MyType -> Either String MyType
boolToStr (B b) =
if not b
then Left "True expected"
else Right (S "Final output")
boolToStr _ = undefined
finalHandler :: Either String MyType -> IO ()
finalHandler (Left err) = putStrLn $ "ERROR: " ++ err
finalHandler (Right myType) = print myType
main :: IO ()
main = do
s <- getLine
strToInt s
(_) debugPrint True
(_) intToBool
(_) debugPrint True
(_) boolToStr
(_) debugPrint True
& finalHandler
我对上述片段有 3 个问题。
- 我想知道是否有任何预定义的运算符可用于链接 中的上述函数。
main
- 有没有更好的方法来表示此代码中的错误流?如果中间输出中有任何值,则控件应跳转到并让它处理该值。
Left
finalHandler
- 我正在处理的主要问题是用 .
我想将值传递给并使用参数有条件地打印它,如果未打印,则返回相同的值。
debugPrint
strToInt s
Right
debugPrint
flag
请忽略未定义的模式匹配,因为它们在我的实际代码中处理。
我试过使用 和 。但我仍然无法了解如何更改抽象以改善代码片段中的数据流。bind
(>=>)
liftIO
答:
有几种方法可以做到这一点,包括显式模式匹配。当然,您始终可以为任何任务定义您的 on 运算符。两者也许都是一个很好的练习。
但是,我认为惯用的解决方案是让所有工作都由 monad 链接完成。
我不知道你对单子的了解有多深;我只想说,“mon”指的是一种奇异感。可以这么说,这意味着一切都是一体的。好吧,这是一个非常模糊的说法;在实践中,您应该记住的是,要单元地链接函数/操作,结果(即在所有人的右侧)需要在所有函数/操作中具有相同的外部类型构造函数。例如,您已经知道这一点 和 ,但它与任何其他单子一样有效。->
IO Int
IO ()
所以这就是你在这里需要实现的,然后你可以只使用普通的 / do 块语法。首先,你需要弄清楚什么是合适的单子,你可以将所有这些操作投射到其中。最简单的选择实际上是只使用 ,毕竟您可以在其中抛出和捕获异常。你可以要求你所有的函数都加入进来——这同样是一个有用的练习,但不是惯用的。>>=
IO
IO
一个更受约束的选项是将错误情况作为显式 monad 转换器放在 之上。您可能希望它被称为 ,它确实存在,但更常用的版本称为 ExceptT
。它仍然是一样的,从字面上看只是一个 newtype 包装器,这是一种通用形式,例如您在代码中拥有的。IO
EitherT
m (Either e a)
IO (Either String MyType)
这个新类型包装器的要点是避免嵌套在(两个单子)内部,而是将它们组合成一个单子,因此可以再次使用旧的组合器。Either ...
IO ...
您仍然需要开始的是一种将结果提升到 monad 的方法。库有 except
,它正是这样做的。Either ...
ExceptT
transformers
对于 ,我将更改其签名中的类型权限:debugPrint
debugPrint :: Bool -> MyType -> ExceptT String IO MyType
对于其他人,我不会更改类型,而是在使用它们时将它们包装起来。except
main
我将实际代码作为练习。
评论
IO
let
strToInt s
let my_int = strToInt s
my_int
main
Left
Right
Either
Either
Left
(>=>)