如何阅读此功能代码
How to read this functional code
阅读(解释)这个实用的米兰达密码时遇到问题。
g = (foldr (+) 0) . (foldr ((:) . ((#) . (:[]))) [])
我知道它的作用
- 通过
#
获取长度来计算列表的大小
- 创建一个包含原始输入列表的上述长度的单元素列表
- 使用
foldr
将新列表折叠成单个整数,并对每个元素执行 +0
但是我对括号感到困惑,看不到输入列表的输入位置。最右边的 []
构造函数做什么?
还有为什么这段代码只能通过函数 g 工作,但如果我直接调用它就会抛出错误?
简而言之,g
是一个returns列表长度的函数。
让我们把函数分成几个部分。
|| returns 1 for any input.
|| return_one "hoobar" = 1
return_one :: * -> num
return_one = (#) . (:[])
|| ignore first argument, insert 1 to the second argument.
|| insert_one "hoobar" [4,5,6] = [1,4,5,6]
insert_one :: *->[num]->[num]
insert_one = (:) . return_one
|| sum of list.
|| sum_list [7,8,9] = 24
sum_list :: [num] -> num
sum_list = foldr (+) 0
|| generate list of 1 which as the same length of original list.
|| change_one ["apple","banana"] = [1,1]
change_one :: [*] -> [num]
change_one = foldr insert_one []
|| return the length of the list.
|| g ["apple","banana"] = 2
g :: [*] -> num
g = sum_list . change_one
我会解释一些令人困惑的函数。
return_one
(:[])
是创建单个元素列表的函数,(#)
returns 长度。
严格来说,(:[])
是 (:)
,它以 []
作为第一个参数。
因此 (:[]) "hoobar" = "hoobar":[] = ["hoobar"]
,并对其应用 (#)
returns 1.
change_one
从空列表[]
开始,遍历列表,在前面插入1。
foldr insert_one [] ["apple","banana"]
= foldr insert_one [1] ["apple"]
= foldr insert_one [1,1] []
我不太了解 Miranda,但基于 Haskell(我相信两者之间的差异很小,只有 #
是列表长度的一元运算符是唯一的半重要的并且 ||
是注释语法):.
是函数组合:
(p . q) x = p (q x)
|| also can be written as:
p . q = \x -> p (q x)
函数组合是关联运算,所以p . (q . r)
= (p . q) . r
= p . q . r
.
利用这些信息,我们可以用 .
的定义展开它:
g = (foldr (+) 0) . (foldr ((:) . ((#) . (:[]))) []) || Original definition
g list = foldr (+) 0 (foldr ((:) . ((#) . (:[]))) [] list)
g list = foldr (+) 0 (foldr (\x -> (:) (((#) . (:[])) x)) [] list)
g list = foldr (+) 0 (foldr (\x -> (:) ((\y -> (#) ((:[]) y)) x)) [] list)
这可以再清理一些:
g list = foldr (+) 0 (foldr (\x -> (:) ((\y -> (#)(y:[])) x)) [] list) || More conventional operator syntax for the innermost `:`
g list = foldr (+) 0 (foldr (\x -> (:) ((#)(x:[]))) [] list) || Innermost lambda was applied to x. Substitute y for x.
g list = foldr (+) 0 (foldr (\x -> (:) ((#)([x]))) [] list) || Apply innermost `:`
g list = foldr (+) 0 (foldr (\x -> (:) #[x])) [] list) || Remove unnecessary parentheses
g list = foldr (+) 0 (foldr (\x acc -> (:) (#[x]) acc) [] list) || Explicitly write implicit argument. This particular step is called eta-expansion
g list = foldr (+) 0 (foldr (\x acc -> (:) 1 acc) [] list) || #[x] is always 1, no matter what x is
g list = foldr (+) 0 (foldr (\x acc -> 1 : acc) [] list) || More conventional syntax for `:`
另请注意,正如您在问题中所述,foldr
并不适用于每个元素 +0
。 foldr op z (a:b:c:[])
变为 op a (op b (op c z)))
(a:b:c:[]
是 [a,b,c]
的另一种写法)。我一直认为这张图有助于理解:
此外,您在直接调用时出错的最可能原因是 p . q x
与 (p . q) x
不同 。
阅读(解释)这个实用的米兰达密码时遇到问题。
g = (foldr (+) 0) . (foldr ((:) . ((#) . (:[]))) [])
我知道它的作用
- 通过
#
获取长度来计算列表的大小
- 创建一个包含原始输入列表的上述长度的单元素列表
- 使用
foldr
将新列表折叠成单个整数,并对每个元素执行+0
但是我对括号感到困惑,看不到输入列表的输入位置。最右边的 []
构造函数做什么?
还有为什么这段代码只能通过函数 g 工作,但如果我直接调用它就会抛出错误?
简而言之,g
是一个returns列表长度的函数。
让我们把函数分成几个部分。
|| returns 1 for any input.
|| return_one "hoobar" = 1
return_one :: * -> num
return_one = (#) . (:[])
|| ignore first argument, insert 1 to the second argument.
|| insert_one "hoobar" [4,5,6] = [1,4,5,6]
insert_one :: *->[num]->[num]
insert_one = (:) . return_one
|| sum of list.
|| sum_list [7,8,9] = 24
sum_list :: [num] -> num
sum_list = foldr (+) 0
|| generate list of 1 which as the same length of original list.
|| change_one ["apple","banana"] = [1,1]
change_one :: [*] -> [num]
change_one = foldr insert_one []
|| return the length of the list.
|| g ["apple","banana"] = 2
g :: [*] -> num
g = sum_list . change_one
我会解释一些令人困惑的函数。
return_one
(:[])
是创建单个元素列表的函数,(#)
returns 长度。
严格来说,(:[])
是 (:)
,它以 []
作为第一个参数。
因此 (:[]) "hoobar" = "hoobar":[] = ["hoobar"]
,并对其应用 (#)
returns 1.
change_one
从空列表[]
开始,遍历列表,在前面插入1。
foldr insert_one [] ["apple","banana"]
= foldr insert_one [1] ["apple"]
= foldr insert_one [1,1] []
我不太了解 Miranda,但基于 Haskell(我相信两者之间的差异很小,只有 #
是列表长度的一元运算符是唯一的半重要的并且 ||
是注释语法):.
是函数组合:
(p . q) x = p (q x)
|| also can be written as:
p . q = \x -> p (q x)
函数组合是关联运算,所以p . (q . r)
= (p . q) . r
= p . q . r
.
利用这些信息,我们可以用 .
的定义展开它:
g = (foldr (+) 0) . (foldr ((:) . ((#) . (:[]))) []) || Original definition
g list = foldr (+) 0 (foldr ((:) . ((#) . (:[]))) [] list)
g list = foldr (+) 0 (foldr (\x -> (:) (((#) . (:[])) x)) [] list)
g list = foldr (+) 0 (foldr (\x -> (:) ((\y -> (#) ((:[]) y)) x)) [] list)
这可以再清理一些:
g list = foldr (+) 0 (foldr (\x -> (:) ((\y -> (#)(y:[])) x)) [] list) || More conventional operator syntax for the innermost `:`
g list = foldr (+) 0 (foldr (\x -> (:) ((#)(x:[]))) [] list) || Innermost lambda was applied to x. Substitute y for x.
g list = foldr (+) 0 (foldr (\x -> (:) ((#)([x]))) [] list) || Apply innermost `:`
g list = foldr (+) 0 (foldr (\x -> (:) #[x])) [] list) || Remove unnecessary parentheses
g list = foldr (+) 0 (foldr (\x acc -> (:) (#[x]) acc) [] list) || Explicitly write implicit argument. This particular step is called eta-expansion
g list = foldr (+) 0 (foldr (\x acc -> (:) 1 acc) [] list) || #[x] is always 1, no matter what x is
g list = foldr (+) 0 (foldr (\x acc -> 1 : acc) [] list) || More conventional syntax for `:`
另请注意,正如您在问题中所述,foldr
并不适用于每个元素 +0
。 foldr op z (a:b:c:[])
变为 op a (op b (op c z)))
(a:b:c:[]
是 [a,b,c]
的另一种写法)。我一直认为这张图有助于理解:
此外,您在直接调用时出错的最可能原因是 p . q x
与 (p . q) x
不同 。