【问题标题】:Haskell function application and curryingHaskell 函数应用和柯里化
【发布时间】:2011-04-22 11:57:05
【问题描述】:

我一直对学习新语言很感兴趣,这一事实让我保持警觉,并使我(我相信)成为一名更好的程序员。我征服 Haskell 的尝试来来去去——到目前为止已经两次——我决定是时候再试一次了。第三次是魅力吧?

不。我重新阅读了我的旧笔记......并感到失望:-(

上次让我失去信心的问题是一个简单的问题:整数的排列。 即从整数列表到列表列表 - 它们的排列列表:

[int] -> [[int]]

这实际上是一个通用问题,因此将上面的 'int' 替换为 'a' 仍然适用。

来自我的笔记:

我自己先编码,我成功了。万岁!

我将我的解决方案发送给我的一个好朋友 - Haskell 大师,这通常有助于向大师学习 - 他给我发送了这个,我被告知,“表达了语言的真正力量,使用通用设施编码您的需求”。就这样吧,我最近喝了酷爱,走吧:

permute :: [a] -> [[a]]
permute = foldr (concatMap.ins) [[]]
   where ins x []     = [[x]]
         ins x (y:ys) = (x:y:ys):[ y:res | res <- ins x ys]

嗯。 让我们分解一下:

bash$ cat b.hs
ins x []     = [[x]]
ins x (y:ys) = (x:y:ys):[ y:res | res <- ins x ys]

bash$ ghci
Prelude> :load b.hs
[1 of 1] Compiling Main             ( b.hs, interpreted )
Ok, modules loaded: Main.

*Main> ins 1 [2,3]
[[1,2,3],[2,1,3],[2,3,1]]

好的,到目前为止,一切都很好。花了我一分钟来理解“ins”的第二行,但是好的: 它将第一个参数放在列表中所有可能的位置。很酷。

现在,了解 foldr 和 concatMap。在“Real world Haskell”中,对 DOT 进行了解释...

(f . g) x

...只是...的另一种语法

f (g x) 

在大师发送的代码中,DOT 是从一个 foldr 中使用的,“ins”函数作为折叠“collapse”:

*Main> let g=concatMap . ins
*Main> g 1 [[2,3]]
[[1,2,3],[2,1,3],[2,3,1]]

好的,既然我想了解DOT是如何被guru使用的,我尝试根据DOT定义的等价表达,(f . g) x = f (g x) ...

*Main> concatMap (ins 1 [[2,3]])

<interactive>:1:11:
     Couldn't match expected type `a -> [b]'
            against inferred type `[[[t]]]'
     In the first argument of `concatMap', namely `(ins 1 [[2, 3]])'
     In the expression: concatMap (ins 1 [[2, 3]])
     In the definition of `it': it = concatMap (ins 1 [[2, 3]])

什么!?!为什么? 好的,我检查了 concatMap 的签名,发现它需要一个 lambda 和一个列表,但那是 只是人类的想法; GHC如何应对?根据上面DOT的定义...

(f.g)x = f(g x), 

...我所做的是有效的,替换方式:

(concatMap . ins) x y = concatMap (ins x y)

挠头……

*Main> concatMap (ins 1) [[2,3]]
[[1,2,3],[2,1,3],[2,3,1]]

所以... DOT 的解释显然是 太简单了... DOT 必须足够聪明才能理解 我们实际上希望“ins”被咖喱带走并“吃掉”第一个 参数 - 从而成为一个只想在 [t] 上操作的函数 (并在所有可能的位置用“1”“散布”它们)。

但是这是在哪里指定的?当我们调用时,GHC 是如何知道这样做的:

*Main> (concatMap . ins) 1 [[2,3]]
[[1,2,3],[2,1,3],[2,3,1]]

“ins”签名是否以某种方式传达了这个……“吃掉我的第一个论点”政策?

*Main> :info ins
ins :: t -> [t] -> [[t]]        -- Defined at b.hs:1:0-2

我看不出有什么特别的——“ins”是一个带“t”的函数, 't' 的列表,然后继续创建一个包含所有“interspersals”的列表。没有什么关于“吃掉你的第一个论点并把它赶走”。

所以……我很困惑。我明白(看了一个小时的代码之后!)发生了什么,但是......上帝全能......也许 GHC 试图看看它可以“剥离”多少个参数?

  let's try with no argument "curried" into "ins",
  oh gosh, boom, 
  let's try with one argument "curried" into "ins",
  yep, works,
  that must be it, proceed)

再次 - 哎呀...

由于我总是将我正在学习的语言与我已经知道的语言进行比较,那么“ins”在 Python 中的外观如何?

a=[2,3]
print [a[:x]+[1]+a[x:] for x in xrange(len(a)+1)]

[[1, 2, 3], [2, 1, 3], [2, 3, 1]]

说实话,现在...哪个更简单?

我的意思是,我知道我是 Haskell 的新手,但我觉得自己像个白痴……看了 4 行代码一个小时,最终假设编译器……尝试各种解释,直到找到“点击”的东西?

引用致命武器的话,“我对这个太老了”......

【问题讨论】:

  • 您可能想为懒惰的人添加一个 TLDR 部分...
  • 我不懂 Haskell,但我同意你所说的。 +1。
  • 我对你的 python 比较有点困惑。您显示的python 代码仅用于ins 函数,对吗?但是 ins 函数并不是您在 Haskell 版本中发现的复杂 - concatMap 是,这是您在 python 版本中遗漏的部分。
  • @tts:顺便说一句,你的 python 代码的 Haskell 等效项是 [take x a ++ [1] ++ drop x a | x &lt;- [0..length(a)]],尽管这比你朋友给你的效率低​​。
  • 公平地说,这些天我的 Python 有点生疏了,但是……你朋友的 ins 对我来说似乎比你的 Python 版本简单得多(或者 @sepp2k 的 Haskell 等效版本,就此而言) .我的意思是,计算列表的长度并按索引拆分?这不是……有点尴尬和困惑吗?更不用说你朋友的ins 是懒惰的,并且适用于无限列表......

标签: haskell functional-programming currying


【解决方案1】:
(f . g) x = f (g x)

这是真的。你由此得出结论

(f . g) x y = f (g x y)

也必须为真,但事实并非如此。事实上,以下是正确的:

(f . g) x y = f (g x) y

这是不一样的。

为什么这是真的? (f . g) x y((f . g) x) y 相同,因为我们知道 (f . g) x = f (g x) 我们可以将其简化为 (f (g x)) y,这又与 f (g x) y 相同。

所以(concatMap . ins) 1 [[2,3]] 等价于concatMap (ins 1) [[2,3]]。这里没有魔法。

解决此问题的另一种方法是通过类型:

. 的类型为 (b -&gt; c) -&gt; (a -&gt; b) -&gt; a -&gt; cconcatMap 的类型为 (x -&gt; [y]) -&gt; [x] -&gt; [y]ins 的类型为 t -&gt; [t] -&gt; [[t]]。因此,如果我们使用concatMap 作为b -&gt; c 参数和ins 作为a -&gt; b 参数,那么a 变为tb 变为[t] -&gt; [[t]]c 变为@987654349 x = [t]y = [t])。

所以concatMap . ins 的类型是t -&gt; [[t]] -&gt; [[t]],这意味着一个函数接受一个whatevers 和一个list 列表(ofwhats)并返回一个list 列表(相同类型的)。​​

【讨论】:

  • 哦,我多么希望大师能像你那样做出回应。当然,我问过他——他证实 Haskell 确实尝试过组合来看看有什么用! ...好吧,你赢了,我第三次去了,潜入...(下一次,顺便说一句,我会问 Stack Overflow :-)
  • Haskell 没有“尝试组合”,它机械地遵循定义。定义是什么?您可以使用 hoogle 和 haddock 快速找到源代码:(.) f g x = f (g x)。所以是的,只有一个论点。
  • @tts:如果你在hoogle中输入.,你会得到一个名为.的函数列表。如果单击第一个结果,您将进入标准(前奏). 函数的文档。如果你点击那里的“source”链接,你会在标准库中看到.的定义,即(.) f g x = f (g x)(这只是(f . g) x = f (g x)的另一种写法)。
  • @ttsiodras:正如 sepp2k 所说,搜索(.),点击search hit,点击黑线鳕文档中提到的source
  • @ttsiodras:我不得不说,要么当你问他时存在某种误解,要么你的朋友并不是真正的“Haskell 大师”......
【解决方案2】:

我想加两分钱。问题和答案听起来就像. 是一个神奇的运算符,它通过重新安排函数调用来做奇怪的事情。事实并非如此。 . 只是函数组合。这是 Python 中的一个实现:

def dot(f, g):
    def result(arg):
        return f(g(arg))
    return result

它只是创建一个新函数,将g 应用于参数,将f 应用于结果,并返回应用f 的结果。

所以(concatMap . ins) 1 [[2, 3]] 是说:创建一个函数concatMap . ins,并将其应用于参数1[[2, 3]]。当您执行concatMap (ins 1 [[2,3]]) 时,您是在说,将函数concatMap 应用于将ins 应用于1[[2, 3]] 的结果——完全不同,正如您从Haskell 可怕的错误消息中发现的那样。

更新:进一步强调这一点。你说(f . g) xf (g x) 的另一种语法。这是错误. 只是一个函数,因为函数可以有非字母数字名称(&gt;&gt;&lt;.. 等,也可以是函数名称)。

【讨论】:

  • 我不认为这在 python 中的工作方式与缺少柯里化的方式相同。
  • 你是对的,我认为.. 我假设合成的单参数函数。您必须将多参数 func 转换为接受一个参数并返回其他 func 的 func 以获得 curry 效果
  • 不管怎样,.. 在 Haskell 中是一个无效的标识符(特殊语法)。
  • @trinithis:但是,(...) 是有效的,(--:)(:::)(&lt;--)(@.@) 以及包含特殊语法作为子字符串的各种其他名称也是有效的。跨度>
  • +1 表示操作符只是函数。运算符是函数对我来说很自然,以至于我没有意识到他认为. 是一种特殊的形式。
【解决方案3】:

你想多了这个问题。您可以使用简单的等式推理来解决所有问题。让我们从头开始尝试:

permute = foldr (concatMap . ins) [[]]

这可以简单地转换为:

permute lst = foldr (concatMap . ins) [[]] lst

concatMap可以定义为:

concatMap f lst = concat (map f lst)

foldr 处理列表的方式是(例如):

-- let lst = [x, y, z]
foldr f init lst
= foldr f init [x, y, z]
= foldr f init (x : y : z : [])
= f x (f y (f z init))

类似

permute [1, 2, 3]

变成:

foldr (concatMap . ins) [[]] [1, 2, 3]
= (concatMap . ins) 1 
    ((concatMap . ins) 2
       ((concatMap . ins) 3 [[]]))

让我们完成第一个表达式:

(concatMap . ins) 3 [[]]
= (\x -> concatMap (ins x)) 3 [[]]  -- definition of (.)
= (concatMap (ins 3)) [[]]
= concatMap (ins 3) [[]]     -- parens are unnecessary
= concat (map (ins 3) [[]])  -- definition of concatMap

现在ins 3 [] == [3],所以

map (ins 3) [[]] == (ins 3 []) : []  -- definition of map
= [3] : []
= [[3]]

所以我们原来的表达式变成了:

foldr (concatMap . ins) [[]] [1, 2, 3]
= (concatMap . ins) 1 
    ((concatMap . ins) 2
       ((concatMap . ins) 3 [[]]))
= (concatMap . ins) 1 
    ((concatMap . ins) 2 [[3]]

让我们一起努力

(concatMap . ins) 2 [[3]]
= (\x -> concatMap (ins x)) 2 [[3]]
= (concatMap (ins 2)) [[3]]
= concatMap (ins 2) [[3]]     -- parens are unnecessary
= concat (map (ins 2) [[3]])  -- definition of concatMap
= concat (ins 2 [3] : [])
= concat ([[2, 3], [3, 2]] : [])
= concat [[[2, 3], [3, 2]]]
= [[2, 3], [3, 2]]

所以我们原来的表达式变成了:

foldr (concatMap . ins) [[]] [1, 2, 3]
= (concatMap . ins) 1 [[2, 3], [3, 2]]
= (\x -> concatMap (ins x)) 1 [[2, 3], [3, 2]]
= concatMap (ins 1) [[2, 3], [3, 2]]
= concat (map (ins 1) [[2, 3], [3, 2]])
= concat [ins 1 [2, 3], ins 1 [3, 2]] -- definition of map
= concat [[[1, 2, 3], [2, 1, 3], [2, 3, 1]], 
          [[1, 3, 2], [3, 1, 2], [3, 2, 1]]]  -- defn of ins
= [[1, 2, 3], [2, 1, 3], [2, 3, 1], 
   [1, 3, 2], [3, 1, 2], [3, 2, 1]]

这里没有什么神奇的。我认为您可能会感到困惑,因为很容易假设concatMap = concat . map,但事实并非如此。同样,它可能看起来像concatMap f = concat . (map f),但这也不是真的。等式推理会告诉你原因。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-18
    • 1970-01-01
    • 2016-05-12
    • 2016-01-01
    • 1970-01-01
    • 2014-04-14
    • 1970-01-01
    • 2021-02-07
    相关资源
    最近更新 更多