将存在类型函数应用于足够多态的参数

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 状态,过渡 stepobserveation 因为 initialobserve 可以实例化以匹配 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 引入的出价是惰性的,不会那样做。