有没有一种方法可以在Haskell中删除重复的where语句?

3

我在Haskell中有以下的代码:

move :: Camera -> (Double, Double, Double) -> Camera
move camera (xt, yt, zt) = camera { cPosition = (x + xt, y + yt, z + zt) }
    where (x, y, z) = cPosition camera

moveForward :: Camera -> Camera
moveForward camera = move camera (-1 * sin ya, 0, -1 * cos ya)
    where (_, ya, _) = cRotation camera

moveBackward :: Camera -> Camera
moveBackward camera = move camera (sin ya, 0, cos ya)
    where (_, ya, _) = cRotation camera
您会注意到moveForwardmoveBackward函数具有相同的where语句。是否有方法可以消除这种重复?我有许多具有相同where子句的函数(读:不止两个)。 我希望不用将其作为另一个参数传递 - 因为它永远不会改变。它将始终是cRotation

1
你是否考虑过从笨拙的(Double, Double, Double)转换到专门的东西,比如Data.Vect - leftaroundabout
1
@leftaroundabout,我有——但是我正在等待两件事情。第一件事是,由于这个话题对我来说很新,每天都会发生变化。第二件事是,我不确定我将不得不优化哪些具体的事情。当事情变得更加具体时,我会看一下Data.Vect,谢谢! - sdasdadas
2个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
7
让这些函数接受元组作为参数,然后用另一个函数将它们包装起来,自动完成提取元组的无聊工作,如何?
rotated :: ((Double, Double, Double) -> Camera -> a) -> Camera -> a
rotated f camera = f (cPosition camera) camera

moveForward :: Camera -> Camera
moveForward = rotated moveForward'
    where moveForward' (_, ya, _) camera = move camera (-1 * sin ya, 0, -1 * cos ya)

moveBackward :: Camera -> Camera
moveBackward = rotated moveBackward'
    where moveBackward' (_, ya, _) camera = move camera (sin ya, 0, cos ya)

编辑:六个月后重新审视我的回答,我发现还有一些可以剥离的重复内容:即move camera调用。所以你的moveForward等函数只需要接收一个三元组并返回一个三元组,如下:

moveRotated :: ((Double, Double, Double) -> (Double, Double, Double)) -> Camera -> Camera
moveRotated f camera = move camera . f $ cPosition camera

moveForward :: Camera -> Camera
moveForward = moveRotated forward
    where forward (_, ya, _) = (- sin ya, 0, - cos ya)

moveBackward :: Camera -> Camera
moveBackward = moveRotated backward
    where backward (_, ya, _) = (sin ya, 0, cos ya)
这样做当然会使moveForwardmoveBackward的能力减少,因为你只能用它们来移动角色。但这样可以将它们简化到自己的本质,并确保你不会意外地做出其他操作。

我想我会使用这个。谢谢! - sdasdadas

2

有一个简单的答案,就是定义您自己的函数。

snd3 :: (a, b, c) -> b
snd3 (a, b, c) = b
然后您可以使用lambda函数。
moveForward camera = \ya -> (-1 * sin ya, 0, -1 * cos ya) $ snd3 $ cRotation camera

moveBackward camera = \ya -> (sin ya, 0, cos ya) $ snd3 $ cRotation camera

如果您想将lens库作为依赖项添加,可以将snd3 cRotation camera替换为cRotation camera ^. _2或等效的view _2 $ cRotation camera。至于删除该lambda,除了定义一个新函数外,没有太多可以做的。

apply3 :: (a -> a') -> (b -> b') -> (c -> c') -> (a, b, c) -> (a', b', c')
apply3 f1 f2 f3 (a, b, c) = (f1 a, f2 b, f3 c)

moveForward = apply3 (negate . sin) (const 0) (negate . cos) . snd3 . cRotation

moveBackward = apply3 sin (const 0) cos . snd3 . cRotation

请使用某些 eta-reduction。

不幸的是,在处理 2-元组时有很多优雅的技巧,但在处理 3-元组时没有那么多。


在我看来,这在第一眼看起来不太易读。(虽然可能是因为我在Haskell方面还不够熟练。)编辑:尽管我要求删除重复,但我正在权衡它与可读性之间的关系。 - sdasdadas
1
@sdasdadas 我不认为apply3特别易读。amalloy的解决方案非常干净,我认为它可能是两者中更好的一个。 - bheklilr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,