一个函数中的两个多态类

2024-01-11

我有状态单子的代码:

import Control.Monad.State

data ModelData = ModelData String
data ClientData = ClientData String

act :: String -> State ClientData a -> State ModelData a
act _ action = do
  let (result, _) = runState action $ ClientData ""
  return result

addServer :: String -> State ClientData ()
addServer _ = return ()

scenario1 :: State ModelData ()
scenario1 = do
  act "Alice" $ addServer "https://example.com"

我试图按照这种方法用多态类型类来概括它:https://serokell.io/blog/tagless-final https://serokell.io/blog/tagless-final.

我可以概括 ModelData:

import Control.Monad.State

class Monad m => Model m where
  act :: String -> State c a -> m a

data Client = Client String

addServer :: String -> State Client ()
addServer _ = return ()

scenario1 :: Model m => m ()
scenario1 = do
  act "Alice" $ addServer "https://example.com"

但是当我尝试同时使用 ModelData 和 ClientData 时,它无法编译:

module ExampleFailing where

class Monad m => Model m where
  act :: Client c => String -> c a -> m a

class Monad c => Client c where
  addServer :: String -> c ()

scenario1 :: Model m => m ()
scenario1 = do
  act "Alice" $ addServer "https://example.com"

错误:

    • Could not deduce (Client c0) arising from a use of ‘act’
      from the context: Model m
        bound by the type signature for:
                   scenario1 :: forall (m :: * -> *). Model m => m ()
        at src/ExampleFailing.hs:9:1-28
      The type variable ‘c0’ is ambiguous
    • In the expression: act "Alice"
      In a stmt of a 'do' block:
        act "Alice" $ addServer "https://example.com"
      In the expression:
        do act "Alice" $ addServer "https://example.com"
   |
11 |   act "Alice" $ addServer "https://example.com"
   |   ^^^^^^^^^^^

我可以用这种方式编译它,但它似乎与我试图概括的原始代码不同:

{-# LANGUAGE MultiParamTypeClasses #-}

module ExamplePassing where

class Monad m => Model m c where
  act :: Client c => String -> c a -> m (c a)

class Monad c => Client c where
  addServer :: String -> c ()

scenario1 :: (Client c, Model m c) => m (c ())
scenario1 = do
  act "Alice" $ addServer "https://example.com"

我非常感谢你的建议。谢谢你!


你的泛化尝试act :: Client c => String -> c a -> m a从技术上讲是正确的:它实际上是原始代码的翻译,但是替换State ModelData with m and State ClientData with c.

发生错误是因为现在“客户端”可以是任何东西,调用者scenario1无法指定它应该是什么。

你看,为了确定哪个版本addServer要调用,编译器必须知道什么c是,但无处可推断!c既不会出现在函数参数中,也不会出现在返回类型中。所以从技术上来说它绝对可以是任何东西,它完全隐藏在里面scenario1。但“绝对任何”对于编译器来说还不够好,因为选择c确定哪个版本addServer被调用,这将决定程序的行为。

这是同一问题的较小版本:

f :: String -> String
f str = show (read str)

这同样不会编译,因为编译器不知道哪个版本show and read打电话。


你有几个选择。

First, if scenario1它本身知道要使用哪个客户端,它可以通过使用来说明TypeApplications:

scenario1 :: Model m => m ()
scenario1 = do
  act "Alice" $ addServer @(State ClientData) "https://example.com"

Second, scenario1可以将此任务卸载给调用它的任何人。为此,您需要声明一个通用变量c即使它没有出现在任何参数或参数中。这可以通过以下方式完成ExplicitForAll:

scenario1 :: forall c m. (Client c, Model m) => m ()
scenario1 = do
  act "Alice" $ addServer @c "https://example.com"

(请注意,你仍然需要做@c让编译器知道哪个版本addServer使用;为了能够做到这一点,你需要ScopedTypeVariables, 包括ExplicitForAll)

那么消费者将不得不做这样的事情:

let server = scenario1 @(State ClientData)

Finally,如果由于某种原因你无法使用TypeApplications, ExplicitForAll, or ScopedTypeVariables,你可以做同样事情的穷人版本 - 使用额外的虚拟参数来引入类型变量(这是以前的做法):

class Monad c => Client c where
  addServer :: Proxy c -> String -> c ()

scenario1 :: (Client c, Model m) => Proxy c -> m ()
scenario1 proxyC = do
  act "Alice" $ addServer proxyC "https://example.com"

(请注意,类方法本身现在也获取了一个虚拟参数;否则将再次无法调用它)

那么消费者将不得不做这件丑陋的事情:

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

一个函数中的两个多态类 的相关文章

随机推荐

  • 如何在 Python 中创建新的未知或动态/expando 对象

    在Python中 我们如何在没有预定义类的情况下创建一个新对象 然后动态地向它添加属性 例子 dynamic object Dynamic dynamic object dynamic property a abc dynamic obje
  • 如何针对特定文件关闭 ARC

    我知道我应该添加编译器标志 fno objc arc到 XCode 4 中的编译源来完成此操作 但它不起作用 即使添加了标志 我仍然遇到错误KeychainItemWrapper m文件声称我需要使用 bridge对于 C 指针 我的项目是
  • 如何通过 WMI 获取 IIS 应用程序信息

    如何使用 WMI 获取 IIS 应用程序 虚拟文件夹 的实际目录路径 Use 查看更多类似示例的工具 On Error Resume Next 常量 wbemFlagReturnImmediately h10 常量 wbemFlagForw
  • 如何限制信标的广告范围?

    是否可以限制信标的范围 以便只有在一定近距离 或接近度 内的设备才能识别并连接到信标 例如 0 5 米区域之外的设备不应该能够看到或连接到信标 我正在使用 iOS 设备作为信标 在苹果的CoreLocationAPI 有一个方法叫具有测量功
  • 如何在Elasticsearch数据库中创建唯一约束?

    我使用elasticsearch作为文档数据库 我创建的每条记录都有一个guid id 系统将其用作记录id 商务人士希望提供一项功能 让用户根据日期以及当天 本月迄今为止创建的记录数拥有自己的自动文件名约定 我需要的是防止重复的用户文件名
  • reg for . 中的双反斜杠和单斜杠有什么区别?

    下面两个命令 grep enrolments grep enrolments 得到了如下所示的相同结果 COMP4001 4368523 Doddau Yobhazravi 8684 M COMP9315 4368523 Doddau Yo
  • 使用“匿名”类实现 Delphi 接口

    我有一个界面 type IProgressObserver interface IInterface procedure ReportProgress Progress Integer procedure ReportError Messa
  • Java 模式匹配器:创建新的还是重置?

    假设一个正则表达式 通过 JavaMatcher对象 与大量字符串进行匹配 String expression The Regular Expression Pattern pattern Pattern compile expressio
  • 跨多个选项卡的工作表过滤脚本在主工作表上显示空白行,并导致其他脚本停止运行

    我有一个多方面的问题 我正在尝试编写一个脚本来提取选项卡名称 并使用该信息从每个单元格中提取特定单元格 文档的选项卡经常更改 以便创建主表 主表旨在显示所有打开的操作项目 并过滤掉任何关闭的项目 空白行 到目前为止 我的脚本可以工作 但它会
  • macOS:如何使用终端在启动后重定向进程的 STDERR / STDOUT?

    在 macOS 中 通常可以通过直接在终端中执行二进制文件来执行进程 从而获得一些可观的控制台 shell 输出 Applications SOME APPLICATION app Contents MacOS SOME APPLICATI
  • 使用 Capistrano 定义捆绑路径

    我在 capistrano 的 deploy rb 文件中使用以下配置 require bundler capistrano require rvm capistrano set bundle cmd home deployment rvm
  • git push 到远程分支

    我有一个有分支的远程项目 所以我首先克隆存储库 然后向克隆发出以下命令以在分支上工作 git checkout b
  • 使用 matplotlib 区分单击和双击

    我正在尝试捕获对我的人物的单击和双击 正如另一篇文章中所说answer https stackoverflow com a 17753195 4537483 the event包含event dblclick这是 False 或 True
  • 在Python中使用函数名作为变量

    我有一个有趣的 可能是愚蠢的 想法 如果我使用内置函数名称作为变量来分配某个对象 比如整数 会发生什么 这是我尝试过的 gt gt gt a 1 2 3 4 gt gt gt len a 4 gt gt gt len 1 gt gt gt
  • 以非交互方式将函数参数传递给 Julia

    我的文件中有一个 Julia 函数 假设是下面的 现在我想将参数传递给这个函数 我尝试做 julia filename jl randmatstat 5 但这给出了一个错误 即 标记是意外的 不确定解决方案是什么 我也对是否有一个 main
  • Socket.io 与 PubNub...为什么?

    我看到 PubNub 说他们支持 Socket io http blog pubnub com node js supercharged by pubnub socket io github http blog pubnub com nod
  • SQL Server 2005 ROW_NUMBER() 不带 ORDER BY

    我正在尝试使用从一个表插入另一个表 DECLARE IDOffset int SELECT IDOffset MAX ISNULL ID 0 FROM TargetTable INSERT INTO TargetTable ID FIELD
  • 内存中“null”在哪里

    在java中 你不能在声明中声明数组的大小 int 5 scores bad 有人告诉我这是因为 JVM 在对象初始化之前不会分配内存空间 如果您有一个实例数组变量 自动初始化为默认值 null 该变量是否指向堆中指示 null 的位置 空
  • 为什么匿名方法中不允许使用 out 参数?

    这不是一个骗局从匿名方法调用带有 ref 或 out 参数的方法 https stackoverflow com questions 1001475 calling a method with ref or out parameters f
  • 一个函数中的两个多态类

    我有状态单子的代码 import Control Monad State data ModelData ModelData String data ClientData ClientData String act String gt Sta