1

我正在尝试在 Haskell 中实现 4 阶 Runge-Kutta,但我发现很难将 Haskell 类型系统用于此任务。有人可以帮忙吗?我希望在以下代码中将“State”和“DState”类型更改为类型类:

data State = State Double deriving (Show)
data DState = DState Double deriving (Show)

update :: State -> DState -> State
update (State x) (DState y) = State (x+y)

add :: DState -> DState -> DState
add (DState x) (DState y) = DState (x + y)

scale :: Double -> DState -> DState
scale h (DState x) = DState (h*x)


update_rk4 :: State -> (Double -> State -> DState) -> Double -> Double -> State
update_rk4 y f t h = update y (scale (h*(1.0/6.0)) s) where
  s = add k1 (add s2 (add s3 k4))
  s2 = scale 2 k2
  s3 = scale 2 k3
  k1 = f t y
  k2 = f (t+0.5*h) ( update y (scale (0.5*h) k1) )
  k3 = f (t+0.5*h) ( update y (scale (0.5*h) k2) )
  k4 = f (t+h) ( update y (scale h k3) )

似乎很难制定类型类,因为 State 和 DState 在某种意义上是交织在一起的,即 State 的特定实例需要 DState 的特定实例。或者是否有其他我看不到的方法?

4

2 回答 2

9

这并不是你想要为你自己的类型类滚动的东西。Haskell 不是一种 OO 语言,您可以在其中为所有内容创建类,而类应该捕获“深层数学概念”。在这个例子中,非常常见的“概念”是您可以将差异更改相加,并且确实已经存在足够多的此类类。

update :: (AffineSpace st, d ~ Diff st) => st -> d -> st
      -- note that idiomatic argument order would be `d -> st -> st` instead.
update = (.+^)

add :: VectorSpace d => d -> d -> d
add = (^+^)

scale :: (VectorSpace d, h ~ Scalar d)
   => h -> d -> d
scale = (*^)


update_rk4 :: (AffineSpace st, d ~ Diff st, VectorSpace d, h ~ Scalar d, Fractional h)
     => st -> (h -> st -> d) -> h -> h -> st
     -- again, more idiomatic order is `(h -> st -> d) -> h -> h -> st -> st`.

至于为什么我建议将st参数放在最后:通常在 Haskell 中部分应用函数,并且η-reduce “流水线参数”。在这个例子中,你很可能想要重用一个特定的“RK4-stepper”,如果st参数是最后一个,你可以简单地用

simStep :: ParticularState -> ParticularState
simStep = update_rk4 f t h
 where f t y = ...
       ...

如果您必须已经在 中绑定了一个y变量simStep y = update_rk4 y f t h,那么您需要在f声明中隐藏它,或者使用笨拙的f t y' = .... 在这种情况下,这并不是一个很大的收获,但是如果您始终如一地应用 η-reduction 的想法,它可以大大清理您的整体代码。

于 2014-10-19T13:09:29.973 回答
0

您可以使用具有功能依赖关系的多参数类型类,尽管这些不是标准的 Haskell 98,而是 GHC 扩展。这些允许您定义一个类型类,其方法不能确定所有类型参数,否则将是模棱两可的。

例如,

class RK4 state dstate | dstate -> state where
    update :: state -> dstate -> state
    add :: dstate -> dstate -> dstate
    scale :: dstate -> dstate -> dstate

如果没有函数依赖,add并且scale会模棱两可,因为对它们的调用不会修复state类型,从而无法解决类型类约束。

在上面的链接中查看更多示例、教程和讨论。另请参阅功能依赖与类型系列的比较,这是其他响应中采用的方法。

于 2014-10-19T13:09:12.410 回答