【问题标题】:Why do Haskell type signature declarations have multiple arrows?为什么 Haskell 类型签名声明有多个箭头?
【发布时间】:2013-12-25 22:25:11
【问题描述】:

抱歉,这措辞不好,但很难描述。

我想我会直接跳到例子:

add                     :: Integer -> Integer -> Integer
add x y                 =  x + y

为什么会这样:

:: Integer -> Integer -> Integer

代替:

:: Integer, Integer -> Integer

箭头是“Function type-mapping operator”,不是某种分隔符,不是吗?

【问题讨论】:

  • 这里的答案可能不清楚,但也请注意(->) 只是一个带有两个参数的常规类型(尽管具有特殊的语法和隐藏的实现)。启动 ghci 并运行 :info (->);您会看到在 Prelude 中甚至已经为 ((->) a) 定义了一些类型类实例
  • 请注意,Clean 是一种非常类似于 Haskell 的语言,您可以省略一些箭头。所以你可以为Int -> Int -> IntInt Int -> Int,或者甚至(对我来说不可读)(a b -> c) [a] [b] -> [c])((a -> b -> c) -> [a] -> [b] -> [c])。解释似乎是subtly different from the one found in Haskell。如果我理解它能够表达一些我们在 Haskell 中无法做出的区别。

标签: haskell syntax types


【解决方案1】:

因为柯里化。想想这个的类型:

add 3 :: Integer -> Integer

如果你给add 一个数字,它会返回一个将Integer 映射到另一个整数的函数。所以你可以这样做:

map (add 3) [1..10]

将参数与部分应用的返回类型区别对待是没有意义的。

编辑澄清

我认为 bheklilr 提出了一个很好的观点,即可以像这样读取类型签名

add :: Integer -> (Integer -> Integer)

我们可以采用具有更多参数的函数,zipWith3,因为这是我唯一能真正想到的。

zipWith3 :: (a -> b -> c -> d) -> [a] -> [b] -> [c] -> [d]

如果我们只阅读 this 的作用,它需要一个函数,该函数接受 3 个值并分别返回第四个和 3 个这些值的列表,并返回第四个值的列表。试试看。

add3 :: Int -> Int -> Int -> Int
add3 a b c = a + b + c

Prelude>zipWith3 add3 [1] [2] [3]
[6]

尽管在这种情况下,所有值都是Int 类型,但它仍然说明了这一点。

现在如果我们不给它所有的列表怎么办?如果我们不给它列表,只给它add3

zipWith3 add3 :: [Int] -> [Int] -> [Int] -> [Int]
zipWith3 add3 :: [Int] -> ([Int] -> [Int] -> [Int])
zipWith3 add3 :: [Int] -> [Int] -> ([Int] -> [Int])

所以,现在我们有了一个接收 3 个列表并返回一个列表的函数。但这也是一个接受列表的函数,返回一个接受 2 个列表并返回一个列表的函数。真的没有办法区分它们。

(zipWith3 add3) [1,2] [3,4] [5,6] :: [Int]
(zipWith3 add3) [1,2] :: [Int] -> [Int] -> [Int]

看看我打算用这个去哪里?参数和返回类型之间没有区别。

【讨论】:

  • 我在学习 Haskell 时变得很头晕,实际上完全不一样。我只有 2 天,所以谢谢你容忍菜鸟问题:)
  • 老实说,我还是有点困惑。我从未定义过部分解决方案功能。这是如何隐式定义的?它是否只是内置在语言中以使用硬编码的一个参数打包函数并在给定较少参数的情况下将其传递?三个或四个参数会发生什么?
  • @BretFontecchio 查看我的编辑,希望对您有所帮助
【解决方案2】:

我的“啊哈”时刻是我意识到这一点的时候

map :: (a -> b) -> [a] -> [b]

当你明确分组时实际上看起来更自然:

map :: ( a -> b )
    -> ([a]->[b])

接受一个函数并返回一个列表函数。使用 uncurried 定义,这种分组不太有效,是吗?

map :: ( a -> b
       ,[a])->[b]

然而,这种分组在某种程度上是一种更“深入”、更有用的函数思考方式。特别是,如果你概括它:

class Functor f where
  fmap :: (a->b) -> f a->f b

functor 做了两件事:获取某种类型(例如 ab)并将其关联到另一种类型(f af b)。但它也需要一个态射a -> b)并将其与另一个态射(f a -> f b)相关联。

【讨论】:

    【解决方案3】:

    类型签名更恰当地理解为

    add :: Integer -> (Integer -> Integer)
    

    这与

    有很大不同
    add :: (Integer -> Integer) -> Integer
    

    第一个是表示接受Integer 并返回一个接受Integer 并返回Integer 的新函数的函数。这是为了方便函数的部分应用,所以像

    (+) :: Int -> Int -> Int  -- specialized to Int
    
    (1 +) :: Int -> Int
    
    > map (1 +) [1..10]
    [2,3,4,5,6,7,8,9,10,11]
    

    【讨论】:

      猜你喜欢
      • 2012-01-12
      • 2011-09-08
      • 1970-01-01
      • 1970-01-01
      • 2012-06-21
      • 1970-01-01
      • 2010-10-27
      • 2022-08-12
      • 2020-01-30
      相关资源
      最近更新 更多