将存在类型函数应用于足够多态的参数
Applying an existentially typed function to a sufficiently polymorphic argument
假设我有一种从状态转换函数生成流的非常简单的方法:
machine :: (s -> s) -> (s -> a) -> s -> [a]
machine next proj s0 = proj <$> iterate next s0
在某些时候,我想使用那个函数而不关心 s
的确切选择。假设我的状态类型有一个类型参数:
data Foo a = Foo1 Int | Foo2 a
并且我有一个状态转换函数,其中包含一些 a
:
{-# LANGUAGE ExistentialQuantification #-}
data Step = forall a. MkStep (Foo a -> Foo a)
step :: Step
step = _
我以类型参数不可知的方式仔细观察我的状态:
observe :: forall a. Foo a -> Int
observe (Foo1 x) = x
observe _ = 0
我的初始状态也是类型参数不可知的:
initial :: forall a. Foo a
initial = Foo1 0
鉴于所有这些有利条件,我希望我可以打包 initial
状态,过渡 step
和 observe
ation 因为 initial
和observe
可以实例化以匹配 step
函数选择的 a
,但没有骰子:
test :: [Int]
test = let MkStep next = step in machine next observe initial
• Couldn't match expected type ‘p’
with actual type ‘Foo a -> Foo a’
because type variable ‘a’ would escape its scope
This (rigid, skolem) type variable is bound by
a pattern with constructor:
MkStep :: forall a. (Foo a -> Foo a) -> Step,
in a pattern binding
at StateMachine.hs:21:12-22
• In the pattern: MkStep next
In a pattern binding: MkStep next = step
In the expression:
let MkStep next = step in machine next observe initial ```
有没有办法在不改变 machine
类型的情况下实现这一点?[1]
有趣的是,使用 Step
作为参数有效:
test2 :: Step -> [Int]
test2 (MkStep next) = machine next observe initial
但我不想沿着这个方向重写我的代码。
[1] 这里我使用的是列表,但对于我的实际用例,machine
是标准库中的 Clash 信号组合器。
test :: [Int]
test = case step of
MkStep next -> machine next observe initial
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ case instead of let
本质上,您必须使用 case
或另一种形式的 "strict" 模式匹配来将存在类型引入范围。 let
引入的出价是惰性的,不会那样做。
假设我有一种从状态转换函数生成流的非常简单的方法:
machine :: (s -> s) -> (s -> a) -> s -> [a]
machine next proj s0 = proj <$> iterate next s0
在某些时候,我想使用那个函数而不关心 s
的确切选择。假设我的状态类型有一个类型参数:
data Foo a = Foo1 Int | Foo2 a
并且我有一个状态转换函数,其中包含一些 a
:
{-# LANGUAGE ExistentialQuantification #-}
data Step = forall a. MkStep (Foo a -> Foo a)
step :: Step
step = _
我以类型参数不可知的方式仔细观察我的状态:
observe :: forall a. Foo a -> Int
observe (Foo1 x) = x
observe _ = 0
我的初始状态也是类型参数不可知的:
initial :: forall a. Foo a
initial = Foo1 0
鉴于所有这些有利条件,我希望我可以打包 initial
状态,过渡 step
和 observe
ation 因为 initial
和observe
可以实例化以匹配 step
函数选择的 a
,但没有骰子:
test :: [Int]
test = let MkStep next = step in machine next observe initial
• Couldn't match expected type ‘p’ with actual type ‘Foo a -> Foo a’ because type variable ‘a’ would escape its scope This (rigid, skolem) type variable is bound by a pattern with constructor: MkStep :: forall a. (Foo a -> Foo a) -> Step, in a pattern binding at StateMachine.hs:21:12-22 • In the pattern: MkStep next In a pattern binding: MkStep next = step In the expression: let MkStep next = step in machine next observe initial ```
有没有办法在不改变 machine
类型的情况下实现这一点?[1]
有趣的是,使用 Step
作为参数有效:
test2 :: Step -> [Int]
test2 (MkStep next) = machine next observe initial
但我不想沿着这个方向重写我的代码。
[1] 这里我使用的是列表,但对于我的实际用例,machine
是标准库中的 Clash 信号组合器。
test :: [Int]
test = case step of
MkStep next -> machine next observe initial
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ case instead of let
本质上,您必须使用 case
或另一种形式的 "strict" 模式匹配来将存在类型引入范围。 let
引入的出价是惰性的,不会那样做。