这个递归柯里化函数有什么问题
What's wrong with this recursive curried function
我正在尝试编写一个函数来解决以下问题;
persistence 39 = 3 // because 3*9 = 27, 2*7 = 14, 1*4=4
// and 4 has only one digit
persistence 999 = 4 // because 9*9*9 = 729, 7*2*9 = 126,
// 1*2*6 = 12, and finally 1*2 = 2
persistence 4 = 0 // because 4 is already a one-digit number
在我解决了这个问题后,我试图让所有函数看起来像这样 Ramda.js 函数样式;
此代码有效;
let multiply = List.reduce (*)
let gt from input = input > from
let just input = fun _ -> input
let ifElse cond trueFn falseFn input =
if cond input then trueFn input else falseFn input
let digits n =
(string n) |> Seq.toList |> List.map (System.Char.GetNumericValue >> int)
let rec persRec iter current =
current
|> digits
|> multiply
|> ifElse (gt 9) (persRec (iter + 1)) (just iter)
let persistence n = if n > 9 then persRec 1 n else 0
但是当我尝试使用像下面这样的柯里化组合版本修改 persRec 函数时,它会导致堆栈溢出。
let rec persRec iter =
digits
>> multiply
>> ifElse (gt 9) (persRec (iter + 1)) (just iter)
这有什么问题?
函数 persRec
正在无条件调用自身。这里:
>> ifElse (gt 9) (persRec (iter + 1)) (just iter)
^^^^^^^^^^^^^^^^^^^^
|
unconditional recursive call
这种情况经常发生。每次persRec
被人调用时,它会立即调用自己。
您可能希望递归调用只在 gt 9
时发生,因为毕竟它在 ifElse
中,对吧?但这没关系:ifElse
并不特殊,它只是一个函数。为了调用函数,F# 必须在调用 之前计算其所有参数 (也称为“求值的应用顺序”),这意味着它必须在它之前调用 persRec (iter + 1)
可以调用 ifElse
,它必须在调用 (>>)
之前调用 ifElse
,并且必须调用 (>>)
才能计算 persRec
的结果。所以最终,它需要调用 persRec
来计算 persRec
的结果。看看这是怎么回事?
之前的版本有效,因为 persRec
的主体在调用 ifElse
之前并未实际执行。 persRec
的主体只有在其所有参数都提供时才会执行,而最后一个参数只有在条件为真时才会在 ifElse
的主体内提供。
在我看来,混淆源于指称语义和操作语义之间的差异。是的,在数学上、逻辑上,函数是等价的。但执行也很重要。正常与应用评估顺序。内存问题。表现。这些都在 lambda 演算的范围之外。
我正在尝试编写一个函数来解决以下问题;
persistence 39 = 3 // because 3*9 = 27, 2*7 = 14, 1*4=4
// and 4 has only one digit
persistence 999 = 4 // because 9*9*9 = 729, 7*2*9 = 126,
// 1*2*6 = 12, and finally 1*2 = 2
persistence 4 = 0 // because 4 is already a one-digit number
在我解决了这个问题后,我试图让所有函数看起来像这样 Ramda.js 函数样式; 此代码有效;
let multiply = List.reduce (*)
let gt from input = input > from
let just input = fun _ -> input
let ifElse cond trueFn falseFn input =
if cond input then trueFn input else falseFn input
let digits n =
(string n) |> Seq.toList |> List.map (System.Char.GetNumericValue >> int)
let rec persRec iter current =
current
|> digits
|> multiply
|> ifElse (gt 9) (persRec (iter + 1)) (just iter)
let persistence n = if n > 9 then persRec 1 n else 0
但是当我尝试使用像下面这样的柯里化组合版本修改 persRec 函数时,它会导致堆栈溢出。
let rec persRec iter =
digits
>> multiply
>> ifElse (gt 9) (persRec (iter + 1)) (just iter)
这有什么问题?
函数 persRec
正在无条件调用自身。这里:
>> ifElse (gt 9) (persRec (iter + 1)) (just iter)
^^^^^^^^^^^^^^^^^^^^
|
unconditional recursive call
这种情况经常发生。每次persRec
被人调用时,它会立即调用自己。
您可能希望递归调用只在 gt 9
时发生,因为毕竟它在 ifElse
中,对吧?但这没关系:ifElse
并不特殊,它只是一个函数。为了调用函数,F# 必须在调用 之前计算其所有参数 (也称为“求值的应用顺序”),这意味着它必须在它之前调用 persRec (iter + 1)
可以调用 ifElse
,它必须在调用 (>>)
之前调用 ifElse
,并且必须调用 (>>)
才能计算 persRec
的结果。所以最终,它需要调用 persRec
来计算 persRec
的结果。看看这是怎么回事?
之前的版本有效,因为 persRec
的主体在调用 ifElse
之前并未实际执行。 persRec
的主体只有在其所有参数都提供时才会执行,而最后一个参数只有在条件为真时才会在 ifElse
的主体内提供。
在我看来,混淆源于指称语义和操作语义之间的差异。是的,在数学上、逻辑上,函数是等价的。但执行也很重要。正常与应用评估顺序。内存问题。表现。这些都在 lambda 演算的范围之外。