如何分析Haskell中的一个函数?

How to analyse a function in Haskell?

大家好Haskell这里是新手。我真的很困惑如何看待柯里化函数。

例如,这里有一个函数定义

zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' _ [] _ = []
zipWith' _ _ [] = []
zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys

当我打电话时

zipWith' (zipWith' (*)) [[1,2,3],[3,5,6]] [[3,2,2],[3,4,5]]

我明白

zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]

等同于

zipWith' :: (a -> b -> c) -> ([a] -> ([b] -> [c]))

不过看不懂的是way/order。我们应该从左到右分析它吗?例如先查看 (a -> b -> c),然后将 [a] 应用于 (a -> b -> c),最后将 ([b] -> [c]) 应用于 (a -> b -> c) -> [a]?还是相反?

如果你不明白我在问什么(抱歉 :( ),你能告诉我当你看到这类问题时你是怎么想的吗?每当我看到这些函数时我都会很害怕,而且它通常花了我一段时间才弄明白:P

柯里化函数看起来比实际情况更可怕。考虑它们如何工作的一种方法是取消它们。例如:

(++) :: [t] -> [t] -> [t]

未柯里化时变为:

(++) :: ([t], [t]) -> [t]

在我看来,这就是所有需要的解释,但这里有一个更详细的解释,可能会完全解释:

a -> b 类型的函数接受类型 a 的参数,return 接受类型 b 的参数。请记住,a -> b 本身就是一个具体类型,可以首先取代 b,从而实现类似 a -> (b -> c).

的功能

假设函数f的类型为a -> b -> c,如上。那是什么意思? 这是一个函数,return是一个函数!这就是柯里化的工作原理。函数的想法 returning 其他函数允许我们 部分应用 函数,限制或指定它们的特征。


现在让我们创建自己的函数,指定:

func :: a -> [a] -> [a]
func = \elem -> ( \list -> elem : list )

这里的 lambda 表达式的编写方式使得类型的含义应该很明显:第一个 lambda (\elem -> ...) 根据它的输入构造另一个。


现在,让我们来看看zipWith:

zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]

zipWith 的第一个参数是一个函数,它本身有两个参数。不太疯狂。

第二个参数是一个可以传递给函数的列表,因为定义了类型变量。

第三个参数是另一个列表,可以在第一个参数之后传递给函数。

return 值是我们首先传递给 zipWith 的函数的结果类型列表。

那么,让我们从压缩功能开始吧。它的类型是a -> (b -> c)。它以 a 类型的参数和 return 将 b 转换为 c 的函数作为参数。它与它的未柯里化等价 (a, b) -> c 同构,不同之处在于我们固定了第一个参数,以获得现在只需要 b 到 return c 的函数。

这就是拉链类型的意思。现在让我们继续 zipWith'。它以压缩函数作为参数(如上所述)。它 return 是 [a] -> ([b] -> [c]) 类型的东西,与 zipper 功能非常相似。我们可以说 zipWith' 提升了压缩功能来处理列表 -zipWith' returns 函数 接受 as 的列表,并且 returns 函数获取 bs 列表和 returns cs 列表。

在您的示例中,我们有 zipWith' (*)。为简单起见,假设 (*)Int -> (Int -> Int) 类型(实际上它更通用 - (Num a) => a -> (a -> a)。然后我们有 a ~ b ~ c ~ IntzipWith' (*) :: [Int] -> ([Int] -> [Int])。然后 zipWith' (*)Int 的列表作为参数,return 将一个 Int 的列表转换为另一个的函数。所以它可以用来压缩两个 a 列表的列表。而这正是发生的事情。

在这种情况下,将嵌套列表视为(二维)矩阵可能会有所帮助。

A =  ( 1 2 3 )   B =  ( 3 2 2 )  -->  result = ( 3  4  6 )
     ( 3 5 6 )        ( 3 4 5 )                ( 9 20 30 )

外部 zip' 将 A 和 B 中的行组合在一起,而内部 zip 将一对行组合成另一行,因此很明显结果将与输入具有相同的形状。

认识到 zipWith (*) 只是向量的分量乘法也可能会有所帮助。其实是有规律的:

zipWith (*)                     -- component-wise * on 1-d matrices
zipWith (zipWith (*))           -- component-wise * on 2-d matrices
zipWith (zipWith (zipWith (*))) -- component-wise * on 3-d matrices
...

当然还有(*)可以用任何二元运算代替。

其他一些列表函数及其矩阵解释:

  • map - 遍历所有行
  • transpose - 转置行和列
  • concat - 展平第一个维度(即将所有行合并为一行)

作为练习,您可以尝试使用列表运算实现矩阵乘法。

从左到右。

其实你可以从4个不同的角度来看它

zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
--  take a function, 2 list and returns a list
zipWith (*)  :: [a] -> [b] -> [c]
-- new function which takes 2 list and returns a new one
zipWith (*) [1,2,3] :: [b] -> [c]
-- new function which takes a list a produce a new one
zipPwith * [1,2,3] [3,5,6] :: [c]
-- a function without argument, or a value (equivalent in Haskell)

或从右到左:-)

zipPwith * [1,2,3] [3,5,6] :: [c]
-- a function without argument, or a value (equivalent in Haskell)    
zipWith (*) [1,2,3] :: [b] -> [c]
-- new function which takes a list a produce a new one 
zipWith (*)  :: [a] -> [b] -> [c]
-- new function which takes 2 list and returns a new one 
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
--  take a function, 2 list and returns a list