如何不使用 Do 符号来书写

2024-04-19

我正在研究可组合的失败,并设法编写了一个具有签名的函数

getPerson :: IO (Maybe Person)

其中一个人是:

data Person = Person String Int deriving Show

它有效,我将其写在 do 符号中,如下所示:

import Control.Applicative

getPerson = do
    name <- getLine -- step 1
    age  <- getInt  -- step 2
    return $ Just Person <*> Just name <*> age 

where

getInt :: IO (Maybe Int)
getInt = do
    n <- fmap reads getLine :: IO [(Int,String)]
    case n of
        ((x,""):[])   -> return (Just x)
        _ -> return Nothing

我编写此函数的目的是创建可组合的可能失败。尽管除了 Maybe 和 IO 之外,我对 monad 的经验很少,但如果我有一个包含更多字段的更复杂的数据类型,链接计算就不会很复杂。

我的问题是如何在没有 do 符号的情况下重写它?由于我无法将值绑定到名称或年龄等名称,因此我不确定从哪里开始。

提出这个问题的原因只是为了提高我对 (>>=) 和 () 的理解,并编写失败和成功的代码(不要让我的代码充满难以辨认的单行话)。

编辑:我想我应该澄清,“我应该如何在没有 do 符号的情况下重写 getPerson”,我不关心 getInt 函数一半。


Do 符号以这种方式将 (>>=) 语法脱糖:

getPerson = do
    name <- getLine -- step 1
    age  <- getInt  -- step 2
    return $ Just Person <*> Just name <*> age

getPerson2 =
  getLine >>=
   ( \name -> getInt >>=
   ( \age  -> return $ Just Person <*> Just name <*> age ))

do 表示法中的每一行,在第一行之后,都被转换为 lambda,然后绑定到前一行。将值与名称绑定是一个完全机械的过程。我根本不明白使用或不使用 do 符号会如何影响可组合性;这严格来说是一个语法问题。

您的其他功能类似:

getInt :: IO (Maybe Int)
getInt = do
    n <- fmap reads getLine :: IO [(Int,String)]
    case n of
        ((x,""):[])   -> return (Just x)
        _ -> return Nothing

getInt2 :: IO (Maybe Int)
getInt2 =
    (fmap reads getLine :: IO [(Int,String)]) >>=
     \n -> case n of
        ((x,""):[])   -> return (Just x)
        _             -> return Nothing

关于您似乎前进的方向的一些指示:

使用时Control.Applicative,使用它通常很有用<$>将纯函数提升到 monad 中。最后一行有一个很好的机会:

Just Person <*> Just name <*> age

becomes

Person <$> Just name <*> age

另外,您应该研究 monad 转换器。这mtl http://hackage.haskell.org/package/mtl软件包最为广泛,因为它随 Haskell 平台一起提供,但还有其他选择。 Monad 转换器允许您通过底层 monad 的组合行为创建一个新的 monad。在本例中,您使用的函数类型为IO (Maybe a)。 mtl(实际上是一个基础库,变压器)定义

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

这与您正在使用的类型相同,带有m变量实例化于IO。这意味着你可以写:

getPerson3 :: MaybeT IO Person
getPerson3 = Person <$> lift getLine <*> getInt3

getInt3 :: MaybeT IO Int
getInt3 = MaybeT $ do
    n <- fmap reads getLine :: IO [(Int,String)]
    case n of
        ((x,""):[])   -> return (Just x)
        _             -> return Nothing

getInt3完全相同,除了MaybeT构造函数。基本上,任何时候你有一个m (Maybe a)你可以把它包起来MaybeT创建一个MaybeT m a。这获得了更简单的可组合性,正如您可以通过新定义看到的那样getPerson3。该函数根本不用担心失败,因为它全部由 MaybeT 管道处理。剩下的一块是getLine,这只是一个IO String。这通过函数提升到 MaybeT monad 中lift.

Editnewacct 的评论建议我也应该提供一个模式匹配示例;除了一个重要的例外之外,它基本上是相同的。考虑这个例子(列表 monad 是我们感兴趣的 monad,Maybe只是用于模式匹配):

f :: Num b => [Maybe b] -> [b]
f x = do
  Just n <- x
  [n+1]

-- first attempt at desugaring f
g :: Num b => [Maybe b] -> [b]
g x = x >>= \(Just n) -> [n+1]

Here g做完全相同的事情f,但是如果模式匹配失败怎么办?

Prelude> f [Nothing]
[]

Prelude> g [Nothing]
*** Exception: <interactive>:1:17-34: Non-exhaustive patterns in lambda

这是怎么回事?这个特殊情况是 Haskell 中最大的缺点之一(IMO)的原因,Monad班级的fail方法。在 do 表示法中,当模式匹配失败时fail叫做。实际的翻译会更接近:

g' :: Num b => [Maybe b] -> [b]
g' x = x >>= \x' -> case x' of
                      Just n -> [n+1]
                      _      -> fail "pattern match exception"

现在我们有

Prelude> g' [Nothing]
[]

fails 的用处取决于 monad。对于列表来说,它非常有用,基本上使模式匹配在列表理解中起作用。在这方面也是非常不错的Maybemonad,因为模式匹配错误会导致计算失败,这正是当Maybe应该Nothing. For IO,也许没有那么多,因为它只是通过抛出用户错误异常error.

这就是完整的故事。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何不使用 Do 符号来书写 的相关文章

随机推荐

  • 如何从 Ansible 中的指定组中删除用户?

    我们假设user01定义了两个组 groupA and groupB 除了主要组之外 我可以将帐户添加到groupC 确保user01属于groupC using user name user01 groups groupC append
  • UITableViewCell 中的自动布局 UILabel

    我正在使用 autoLayout 并且对它很陌生 我有一个如下所示的 UITableView Cell UITableViewCell 高度是动态的 因此它的高度根据文本而变化 我有两个标签 A 和 B 它们可以有可变长度的文本 但宽度是固
  • 使用 ffmpeg 将带有 Alpha 的 .mov 转换为带有 alpha 的 .webm 时出现错误“使用 auto_alt_ref 进行透明度编码不起作用”

    我正在尝试将具有 alpha 透明度的 mov 文件转换为 webm 文件 并一直在关注此线程以寻求帮助 使用 ffmpeg 将带 Alpha 的 mov 转换为带 Alpha 的 VP9 Webm https stackoverflow
  • 从 URL 中删除特殊字符

    我想删除网址中的任何特殊字符 我尝试使用 StringEscapeUtils 类来删除特殊字符 但那没有用 例如 http 3A 2Ffeeds feedburner com 2FNdtvNews TopStories 欢迎任何想法 我过去
  • 如何将字符串转换为按类型

    如何将 String 转换为 By 类型 以下是我的场景 按以下方式将对象标识保留在属性文件中 username By id username password By id password 在应用程序中我想检索像 Properties p
  • 如何在 ul 元素上触发 focusout 事件?

    我有一个可以改变外观的插件select所有浏览器上的 html 标签 我正在尝试使新样式的元素集表现得像正常元素一样select标签 我快到了 但我只需要弄清楚一件事 那就是如何隐藏ul集中注意力 首先 这是新的 select 元素的演示
  • 如何在swift 2中通过短信发送验证码

    我为我的应用程序构建了一个注册表单 我需要通过短信向用户发送验证码才能完成注册过程 我尝试使用 MFMessageComposeViewController 但它打开设备上的对话框短信 以便用户可以看到代码 我还检查了网络上是否有发送短信的
  • Django 中 DateTimeField 的过期逻辑

    我在服务模型中有一个 DateTimeField 定义如下 pub date models DateTimeField default timezone now blank True 我想在我的代码中创建一些过期逻辑 以便超过 2 个月的服
  • 有没有办法在运行时找到 PackageFamilyName?

    有没有办法在运行时找出 PackageFamilyName 如清单中所示 我查看了 Program Files WindowsApps 但找不到相关的字符串 找不到任何 API 可以让我这样做 欢迎任何其他想法 我想你正在寻找的是包 ID
  • Discord.py:wait_for('reaction_add')未按预期工作

    我正在尝试在discord py 上制作一个常见问题解答机器人 到目前为止进展顺利 我想添加一个额外的功能 当机器人检测到常见问题解答时 机器人不会直接发送答案 而是会发送一条提示消息 其中包含两种反应 竖起大拇指和竖起大拇指 具体取决于所
  • 错误:“defaultfloat”不是“std”的成员

    std defaultfloat尽管在 GCC 中似乎没有定义在标准中 http www cplusplus com reference ios defaultfloat 我认为是 27 5 6 4 我将其隔离为这个简单的程序 test c
  • FirebaseInstanceId 不再存在

    我刚刚升级了所有 Firebase 依赖项 但遇到了问题 FirebaseInstanceId不再被认可 我用它来获取令牌 id 如下所示 String tokenId FirebaseInstanceId getInstance getT
  • Mercurial 工作流程是否可以通过未提交的更改进行更新?

    因此 我的网站从 CVS 切换到 Mercurial 我遇到的最大问题是 如果我正在处理一些我不想提交的文件 我只需保存它们 然后我还有其他文件想要推送到服务器 但是如果其他人已经制作了对存储库进行更改 然后我将它们拉下来 它要求我合并或变
  • 如何在flutter中显示base64格式的pdf?

    我有 base64 格式的 pdf 内容 我需要在 flutter 应用程序的容器中显示它 我正在尝试使用 dart 中的图像 pdf 和转换包来完成此操作 但我收到图像类错误 并且我不知道如何显示图像 List
  • Android后台服务判断前台应用程序

    我正在开发一个应用程序 可以监控全天的手机使用情况 为此 我有一个后台服务 该服务在设备启动时启动 并不断轮询以找出当前的前台应用程序是什么 当我单击一个应用程序然后退出并单击另一个应用程序时 以下代码将起作用 现在假设我打开浏览器并转到另
  • 多个进程可以共享一个 CUDA 上下文吗?

    这个问题是 Jason R 的后续问题comment https stackoverflow com questions 29964392 multiple cuda contexts for one device any sense co
  • 使用一个控件将两个 Twitter Bootstrap 轮播链接在一起

    我刚刚学习 javascript 所以请耐心等待 我正在尝试使用相同的控件在引导程序中启动两个轮播 按照目前的设置方式 包含轮播 ID 的 href 标签控制着每个轮播 有人对如何使用相同的控件以最少的修改将两者链接在一起有建议吗 这是 H
  • 如何记录 Java Record 参数?

    应该如何记录Java记录 https openjdk java net jeps 359参数 我指的是最终成为构造函数参数 类字段的参数 I tried param name the name of the animal param age
  • Log4j 2 创建记录器时挂起

    我有这个简单的程序 package myPackage import org apache logging log4j Logger import org apache logging log4j LogManager public cla
  • 如何不使用 Do 符号来书写

    我正在研究可组合的失败 并设法编写了一个具有签名的函数 getPerson IO Maybe Person 其中一个人是 data Person Person String Int deriving Show 它有效 我将其写在 do 符号