Haskell 类型对库不透明但特定于应用程序的函数

Haskell 类型对库不透明但特定于应用程序的函数,haskell,types,Haskell,Types,我想传递一个函数: f :: a -> CmdRequest -> (a, CmdResponse) 从应用程序A到系统S 定义CmdRequest和CmdResponse 初始化时:给定/存储f 初始化时:存储a的初始值(作为不透明值,S无法看到任何a结构) 调用f(作为系统操作的一部分) 传递它以前存储的值(来自以前的调用或系统初始化) 存储返回类型的第一个组件 A 定义f 初始化时:将f传递到S 初始化时:将a的初始值传递给S 可以为a使用特定类型(例如Data.Ha

我想传递一个函数:

f :: a -> CmdRequest -> (a, CmdResponse)
从应用程序A到系统S

  • 定义CmdRequest和CmdResponse
  • 初始化时:给定/存储
    f
  • 初始化时:存储
    a
    的初始值(作为不透明值,S无法看到任何
    a
    结构)
  • 调用
    f
    (作为系统操作的一部分)
  • 传递它以前存储的值(来自以前的调用或系统初始化)
  • 存储返回类型的第一个
    组件
A

  • 定义
    f
  • 初始化时:将
    f
    传递到S
  • 初始化时:将
    a的初始值传递给S
  • 可以为
    a
    使用特定类型(例如Data.HashMap)
  • 当调用
    f
    时,它可以看到
    a
在Haskell/GHC中如何做到这一点

S状态的类型签名是什么? 我认为它可以简单到

State a :: ...
但是有没有办法隐藏
a
?(其中“隐藏”表示不将类型参数指定为
状态

我最初尝试了类型类和存在类型,但被卡住了

A的状态
初始化
.                              .                       .
.                              .                       .
.                              .                       .
|                              |                       |
x------通过f------>|
|x-----商店f------>|
x-------a------>|
|x------存储a------->|
|                       |
.                       .
.                       .
.                       .
系统运行
|                       |
|                       |
x------得到f------->|
|                       |
x------得到一个------->|
|                       |
||
|                              |                       |
.                              .                       .
.                              .                       .
.                              .                       .

| 拥有一个对库不透明但特定于应用程序的函数是Haskell的谋生之道。例如,从“容器”包考虑。 映射,
m::map ka
是一段数据,它将键
k
与值
A
关联起来。Data.Map库不需要知道其值的结构,事实上,在
k
a
中保持
Map
多态性的定义会阻止库对键和值的内容做任何事情。嗯,几乎什么都有。typeclas稍微改变了对话,如果用户提供了一个关于键或值的函数,那么它们可以被改变。然而,重要的一点仍然是,多态参数限制了在库中可以做什么

您可以尝试使用存在类型来摆脱多态参数。就GitHub代码而言,您可以使用以下内容定义状态:

{-# Language ExistentialQuantification #-}
import           Control.Concurrent.MVar

data State = forall a . State
  {
    _nextValue     :: MVar a
  , _applyLogEntry :: a -> CmdRequest -> (a, CmdResponse)
  }
其中,为了清晰起见,我已将monad参数从
数据状态ma
中删除。值参数
a
,现在被存在量化隐藏。检索存储的值成为一个关键点。下面的代码

getStVal :: State -> IO a
getStVal st@(State mva f) = takeMVar mva
抛出编译错误,指出函数的预期类型是
IO a
(由于
getStVal
签名),但实际类型是
IO a1
(由于
State
类型)。这是因为
getStVal
要求一个特定的(尽管在编译时未知)类型,而数据定义说该值可以是任何类型(但我们不能在编译时假设任何特定类型)

a
的存储值传递给App也没有帮助,因为App无法根据a的值改变其行为。状态值可以更新,但不能直接检查。(您可以包括typeclass信息,以表明某些方法(例如show)可以应用于该类型。)


您还可以尝试其他扩展。最终,我不确定GADTs或RankNTypes会有更好的表现。您必须以某种方式说服编译器,您的程序将能够使用未知类型。但实际上,多态参数通常是实现这一点的最佳方法

为什么需要做一些特殊的事情?让L的API包含诸如
fooWithCallback::(a->CmdRequest->(a,CmdResponse))->{-type of foo actions-}
之类的函数有什么错?foo操作的类型必须有一个
a
的参数,但它确实不需要任何技巧或艰苦的工作。它具有类型
iorefa->(a->(a,b))->iob
,这与类型
(a->()->(a,b))->iorefa->iob
基本相同;眯着眼睛看,你会发现
()
是一种(特别无聊的)
CmdRequest
b
是一种
CmdResponse
,而
IORef a->IO b
是一种foo操作,使其符合我在之前的评论中提出的形状。我看不出有任何问题。请记住,如果定义了
f
并且
CmdRequest
getStVal :: State -> IO a
getStVal st@(State mva f) = takeMVar mva