代码之家  ›  专栏  ›  技术社区  ›  davidA

Haskell:将参数组合成元组而不是使用不同的参数意味着什么?

  •  0
  • davidA  · 技术社区  · 6 年前

    作为Haskell的初学者,我对最佳实践很好奇。特别是,在没有其他需求的情况下,最好是使用元组关联相关函数参数,还是保持它们“赤裸裸”?

    例如。

    vector :: Float -> Float -> Float -> Vector
    

    与。

    vector :: (Float, Float, Float) -> Vector
    

    当涉及到其他参数时,元组的使用似乎可以清楚地表明某一组参数是相互关联的。但当函数仅将元组作为参数时,它也会使代码更加冗长。

    1 回复  |  直到 6 年前
        1
  •  8
  •   leftaroundabout    6 年前

    根据经验,我建议 从未 在函数签名的参数中放入元组。

    为什么?好吧,如果重点是把东西组合在一起,那么元组在这方面做的工作相当微薄。当然,您可以使用嵌套元组和类型同义词来解释它们的含义,但所有这些都是脆弱的,使用适当的记录类型可以更好、更安全地完成。如你所知,向量的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)]


    你呢 可以 也有带元组的多结果函数。例如,您需要使用延续传递样式 splitAt :: Int -> [a] -> ([a],[a]) 变成 splitAt' :: Int -> [a] -> ([a] -> [a] -> r) -> r .

        2
  •  1
  •   Bjartur Thorlacius    6 年前

    curry 和 uncurry curry3 f a b c= f(a,b,c) 和 uncurry3 f (a,b,c)= f a b c

    在本例中,对于大多数用途,我将使用命名的数据类型。事实上,你似乎已经有了 Vector 类型。做你的构装师, vector