Cloud Haskell - 如何为闭包编写 "pure"?
Cloud Haskell - How to write "pure" for Closures?
我一直在玩 Cloud Haskell. I've noticed in the hackage documentation 有一种应用程序界面。但特别是我正在尝试查找或编写具有以下签名的函数 closurePure
:
closurePure :: (Typeable a, Binary a) => a -> Closure a
这基本上是pure的限制版。
虽然 Closure
数据类型本身是抽象的,但以下 closure
提供:
closure :: Static (ByteString -> a) -> ByteString -> Closure a
所以我可以做到这一点:
closurePure :: (Typeable a, Binary a) => a -> Closure a
closurePure x = closure ??? (encode x)
问题是在 ???
所在的位置放什么。
我的第一次尝试如下:
myDecode :: (Typeable a, Binary a) => Static (ByteString -> a)
myDecode = staticPtr (static decode)
但阅读 GHC docs on static pointers, the show
example suggested to me that you can't have a constraint because a constrained function doesn't have a Typeable
instance. So I tried the work around suggested using Dict
:
myDecode :: Typeable a => Static (Dict (Binary a) -> ByteString -> a)
myDecode = staticPtr (static (\Dict -> decode))
但现在我得到了不适合上面 closure
函数的错误类型。
有没有写 closurePure
或类似的东西(或者我在 Cloud Haskell 文档中错过了它)?将 binary
普通类型提升到 Closure
s 似乎对于使用给定的应用程序接口至关重要,但我不知道如何去做。
请注意,我可以这样做:
class StaticDecode a where
staticPtrDecode :: StaticPtr (ByteString -> a)
instance StaticDecode Int where
staticPtrDecode = static Data.Binary.decode
instance StaticDecode Float where
staticPtrDecode = static Data.Binary.decode
instance StaticDecode Integer where
staticPtrDecode = static Data.Binary.decode
-- More instances etc...
myPure :: forall a. (Typeable a, StaticDecode a, Binary a) => a -> Closure a
myPure x = closure (staticPtr staticPtrDecode) (encode x)
效果很好,但基本上需要我为每个 Binary
实例重复一个实例。看起来很乱,我更喜欢另一种方式。
让我们花点时间考虑一下您的要求。回想一下,typeclasses 基本上是 shorthand 用于字典传递。所以让我们重写:
data BinaryDict a = BinaryDict
{ bdEncode :: a -> ByteString
, bdDecode :: ByteString -> a
}
现在你想写一个函数:
closurePure :: (Typeable a) => BinaryDict a -> a -> Closure a
您的尝试是:
closurePure bdict = closure (staticPtr (static (bdDecode bdict))) . bdEncode bdict
现在我们可以清楚地看到发生了什么,我们可以看到 static
的论点无法结束。如果 BinaryDict
s 被允许随意创建,比如说从用户数据,这个功能将是不可能的。我们需要:
closurePure :: (Typeable a) => Static (BinaryDict a) -> a -> Closure a
也就是说,我们需要静态指针 table 中所需 Binary
个实例的条目。因此您的枚举解决方案,以及为什么我怀疑需要这样的解决方案。我们也不能指望自动枚举它太,因为有无限多的实例。
然而,这对我来说似乎很愚蠢,因为实例似乎正是您希望自动成为静态的那种东西。它们本质上是静态的(那是什么,reflection
?我听不见你说话)。这可能至少在分发的 Haskell 论文中被反思过(我没有读过)。
我们通常可以通过简单地创建一个 class 来解决这个问题,该 class 具体枚举每个 class 的每个实例(déjà vu?)。
class c => StaticConstraint c where
staticConstraint :: StaticPtr (Dict c)
instance StaticConstraint (Show Int) where
staticConstraint = static Dict
-- a handful more lines...
严肃一点,如果你真的不想枚举(我不怪你),你至少可以通过调用约定来减轻痛苦:
closurePure :: (Typeable a, Binary a) => StaticPtr (ByteString -> a) -> a -> Closure a
closurePure decodePtr = closure (staticPtr decodePtr) . encode
someClosure :: Closure Int
someClosure = closurePure (static decode) 42
这个废话是必要的,因为 static
是一个 "syntactic form" 而不是一个函数——通过提及它,我们表明 Int
的 Binary
实例实际上必须生成并记录在静态指针table.
如果你觉得厚脸皮,你可以启用 {-# LANGUAGE CPP #-}
和
-- PURE :: (Binary a, Typeable a) => a -> Closure a, I promise
#define PURE (closurePure (static decode))
someClosure :: Closure Int
someClosure = PURE 42
也许有一天 Haskell 会迈出下一步,升级到经过时间考验的 Segmentation fault (core dumped)
它的前辈,而不是吐出那些傲慢的类型错误。
你说得对,Closure
有一个应用性的- 类 结构,这一事实在 [=29= 的接口和实现中变得更加明确].它不太适用,因为在 pure
的情况下,我们确实有额外的约束,即参数必须以某种方式可序列化。
其实我们有更强的约束。不仅参数必须是可序列化的,约束本身也必须是可序列化的。就像很难直接序列化函数一样,你可以想象很难序列化约束。但就像函数一样,诀窍是将 静态指针 序列化到约束本身,如果存在这样的静态指针的话。我们怎么知道这样的指针存在呢?我们可以引入一个类型 class,在给定约束的情况下,用一个方法为我们提供指针的名称:
class GimmeStaticPtr c where
gimmeStaticPtr :: StaticPtr (Dict c)
这里有一个小技巧。 StaticPtr
的类型索引的种类是 *
的种类,而约束的种类是 Constraint
。因此,我们重用了 constraints 库中的一个技巧,该技巧包括将约束包装到数据类型(上面的 Dict
)中,就像所有数据类型一样,它属于 *
类型。具有关联 GimmeStaticPtr
实例的约束称为 静态约束 。
一般来说,有时组合静态约束以获得更多静态约束很有用。 StaticPtr
不可组合,但 Closure
是。所以 distributed-closure
实际上做的是定义一个类似的 class,我们称之为
class GimmeClosure c where
gimmeClosure :: Closure (Dict c)
现在我们可以用与您类似的方式定义 closurePure
:
closurePure :: (Typeable a, GimmeClosure (Binary a)) => a -> Closure a
如果将来编译器可以通过根据需要生成静态指针来即时解析 GimmeClosure
约束,那就太好了。但就目前而言,最接近的是模板 Haskell。分布式闭包提供了一个模块,可以在 class Cls
的定义站点自动生成 GimmeClosure (Cls a)
约束。参见 withStatic
here。
顺便说一句,Edsko de Vries 给出了 great talk 关于分布式闭包及其中体现的想法。
我一直在玩 Cloud Haskell. I've noticed in the hackage documentation 有一种应用程序界面。但特别是我正在尝试查找或编写具有以下签名的函数 closurePure
:
closurePure :: (Typeable a, Binary a) => a -> Closure a
这基本上是pure的限制版。
虽然 Closure
数据类型本身是抽象的,但以下 closure
提供:
closure :: Static (ByteString -> a) -> ByteString -> Closure a
所以我可以做到这一点:
closurePure :: (Typeable a, Binary a) => a -> Closure a
closurePure x = closure ??? (encode x)
问题是在 ???
所在的位置放什么。
我的第一次尝试如下:
myDecode :: (Typeable a, Binary a) => Static (ByteString -> a)
myDecode = staticPtr (static decode)
但阅读 GHC docs on static pointers, the show
example suggested to me that you can't have a constraint because a constrained function doesn't have a Typeable
instance. So I tried the work around suggested using Dict
:
myDecode :: Typeable a => Static (Dict (Binary a) -> ByteString -> a)
myDecode = staticPtr (static (\Dict -> decode))
但现在我得到了不适合上面 closure
函数的错误类型。
有没有写 closurePure
或类似的东西(或者我在 Cloud Haskell 文档中错过了它)?将 binary
普通类型提升到 Closure
s 似乎对于使用给定的应用程序接口至关重要,但我不知道如何去做。
请注意,我可以这样做:
class StaticDecode a where
staticPtrDecode :: StaticPtr (ByteString -> a)
instance StaticDecode Int where
staticPtrDecode = static Data.Binary.decode
instance StaticDecode Float where
staticPtrDecode = static Data.Binary.decode
instance StaticDecode Integer where
staticPtrDecode = static Data.Binary.decode
-- More instances etc...
myPure :: forall a. (Typeable a, StaticDecode a, Binary a) => a -> Closure a
myPure x = closure (staticPtr staticPtrDecode) (encode x)
效果很好,但基本上需要我为每个 Binary
实例重复一个实例。看起来很乱,我更喜欢另一种方式。
让我们花点时间考虑一下您的要求。回想一下,typeclasses 基本上是 shorthand 用于字典传递。所以让我们重写:
data BinaryDict a = BinaryDict
{ bdEncode :: a -> ByteString
, bdDecode :: ByteString -> a
}
现在你想写一个函数:
closurePure :: (Typeable a) => BinaryDict a -> a -> Closure a
您的尝试是:
closurePure bdict = closure (staticPtr (static (bdDecode bdict))) . bdEncode bdict
现在我们可以清楚地看到发生了什么,我们可以看到 static
的论点无法结束。如果 BinaryDict
s 被允许随意创建,比如说从用户数据,这个功能将是不可能的。我们需要:
closurePure :: (Typeable a) => Static (BinaryDict a) -> a -> Closure a
也就是说,我们需要静态指针 table 中所需 Binary
个实例的条目。因此您的枚举解决方案,以及为什么我怀疑需要这样的解决方案。我们也不能指望自动枚举它太,因为有无限多的实例。
然而,这对我来说似乎很愚蠢,因为实例似乎正是您希望自动成为静态的那种东西。它们本质上是静态的(那是什么,reflection
?我听不见你说话)。这可能至少在分发的 Haskell 论文中被反思过(我没有读过)。
我们通常可以通过简单地创建一个 class 来解决这个问题,该 class 具体枚举每个 class 的每个实例(déjà vu?)。
class c => StaticConstraint c where
staticConstraint :: StaticPtr (Dict c)
instance StaticConstraint (Show Int) where
staticConstraint = static Dict
-- a handful more lines...
严肃一点,如果你真的不想枚举(我不怪你),你至少可以通过调用约定来减轻痛苦:
closurePure :: (Typeable a, Binary a) => StaticPtr (ByteString -> a) -> a -> Closure a
closurePure decodePtr = closure (staticPtr decodePtr) . encode
someClosure :: Closure Int
someClosure = closurePure (static decode) 42
这个废话是必要的,因为 static
是一个 "syntactic form" 而不是一个函数——通过提及它,我们表明 Int
的 Binary
实例实际上必须生成并记录在静态指针table.
如果你觉得厚脸皮,你可以启用 {-# LANGUAGE CPP #-}
和
-- PURE :: (Binary a, Typeable a) => a -> Closure a, I promise
#define PURE (closurePure (static decode))
someClosure :: Closure Int
someClosure = PURE 42
也许有一天 Haskell 会迈出下一步,升级到经过时间考验的 Segmentation fault (core dumped)
它的前辈,而不是吐出那些傲慢的类型错误。
你说得对,Closure
有一个应用性的- 类 结构,这一事实在 [=29= 的接口和实现中变得更加明确].它不太适用,因为在 pure
的情况下,我们确实有额外的约束,即参数必须以某种方式可序列化。
其实我们有更强的约束。不仅参数必须是可序列化的,约束本身也必须是可序列化的。就像很难直接序列化函数一样,你可以想象很难序列化约束。但就像函数一样,诀窍是将 静态指针 序列化到约束本身,如果存在这样的静态指针的话。我们怎么知道这样的指针存在呢?我们可以引入一个类型 class,在给定约束的情况下,用一个方法为我们提供指针的名称:
class GimmeStaticPtr c where
gimmeStaticPtr :: StaticPtr (Dict c)
这里有一个小技巧。 StaticPtr
的类型索引的种类是 *
的种类,而约束的种类是 Constraint
。因此,我们重用了 constraints 库中的一个技巧,该技巧包括将约束包装到数据类型(上面的 Dict
)中,就像所有数据类型一样,它属于 *
类型。具有关联 GimmeStaticPtr
实例的约束称为 静态约束 。
一般来说,有时组合静态约束以获得更多静态约束很有用。 StaticPtr
不可组合,但 Closure
是。所以 distributed-closure
实际上做的是定义一个类似的 class,我们称之为
class GimmeClosure c where
gimmeClosure :: Closure (Dict c)
现在我们可以用与您类似的方式定义 closurePure
:
closurePure :: (Typeable a, GimmeClosure (Binary a)) => a -> Closure a
如果将来编译器可以通过根据需要生成静态指针来即时解析 GimmeClosure
约束,那就太好了。但就目前而言,最接近的是模板 Haskell。分布式闭包提供了一个模块,可以在 class Cls
的定义站点自动生成 GimmeClosure (Cls a)
约束。参见 withStatic
here。
顺便说一句,Edsko de Vries 给出了 great talk 关于分布式闭包及其中体现的想法。