dav*_*idA 2 parameters haskell functional-programming tuples interface
作为 Haskell 初学者,我对最佳实践很好奇。特别是,在没有其他要求的情况下,是使用元组关联相关的函数参数,还是保持“裸”更好?
例如
vector :: Float -> Float -> Float -> Vector
对比
vector :: (Float, Float, Float) -> Vector
我问的原因是有时参数的某些方面(例如 2D 或 3D 点或向量中的 x 坐标)通常与其他参数(例如 y 和 z 坐标)相关联。我可以看到在这两种情况下如何使用模式匹配,但我很想知道使用元组或不同参数是否有严重的影响。
当涉及到其他参数时,元组的使用似乎清楚地表明某组参数是相互关联的。但是当函数只将元组作为参数时,它也会使代码更加冗长。
根据经验,我建议永远不要将元组放在函数签名的参数中。
为什么?好吧,如果重点是将东西组合在一起,那么元组在这方面做得相当糟糕。当然,您可以使用嵌套元组和类型同义词来解释它们的含义,但所有这些都是脆弱的,并且使用适当的记录类型会更好、更安全。正如您所确定的,向量的 x 和 y 分量通常会聚在一起。不仅如此,在许多意义上,让 x 和 y 分量对任何有趣的代码完全隐藏是一个好主意。这正是Vector类型应该完成的。(可能应该调用Vector3或?³替代。)该vector函数的唯一目的应该是从组件中组装其中之一。好吧,如果这是它唯一要做的事情,那么这三个组件是唯一的争论,并且没有必要将它们进一步组合在一起......这基本上只是将一个手提箱放入另一个运输箱中。最好立即将正确的容器用作单个包装器。
vector3 :: Float -> Float -> Float -> Vector3
常用函数签名中元组的一个例子是
randomR :: (Random a, RandomGen g) => (a,a) -> g -> (a,g)为什么这是一个坏主意?好吧,您使用元组来表示一个区间……但在结果中也表示完全不同的东西,即获得的随机值与更新后的生成器的分组。做到这一点的正确方法是拥有一个正确表达它的类型
data Interval a = Interval {lowerBound, upperBound :: a}
randomR :: (Random a, RandomGen g) => Interval a -> g -> (a,g)
...或者更好的是,将关注点分开,即手动状态线程应该隐藏在合适的 monad 中——例如RVar. 此时范围限制成为唯一的参数,因此您不再需要将它们组合在一起!
uniform :: Distribution Uniform a => a -> a -> RVar a这并不意味着您根本不应该使用元组。对于结果值,柯里化机制并不容易†,因此如果您有一个返回两个结果的函数,但对于这两个值一起表示的内容没有任何有意义的解释,那么,返回一个元组。
此外,如果您将完全抽象的类型组合在一起,则不可能对它们组合在一起的含义进行解释。这就是为什么zip :: [a] -> [b] -> [(a,b)]给出元组列表的原因。
†您可以也有元组多结果的函数。为此,您需要使用 continuation-passing 样式,例如splitAt :: Int -> [a] -> ([a],[a])become splitAt' :: Int -> [a] -> ([a] -> [a] -> r) -> r。
| 归档时间: | 
 | 
| 查看次数: | 79 次 | 
| 最近记录: |