F# 尾部递归映射#

F# 尾部递归映射#,f#,tail-recursion,map-function,F#,Tail Recursion,Map Function,我想编写一个尾部递归函数,将列表中的所有值乘以F#中的2。我知道有很多方法可以做到这一点,但我想知道这是否是一个可行的方法。这纯粹是为了教育目的。我意识到有一个内置的函数为我做这件事 let multiply m = let rec innerfunct ax = function | [] -> printfn "%A" m | (car::cdr) -> (car <- car*2 innerfunct cdr); innerfunct m;;

我想编写一个尾部递归函数,将列表中的所有值乘以F#中的2。我知道有很多方法可以做到这一点,但我想知道这是否是一个可行的方法。这纯粹是为了教育目的。我意识到有一个内置的函数为我做这件事

 let multiply m =
    let rec innerfunct ax = function
    | [] -> printfn "%A" m
    | (car::cdr) -> (car <- car*2 innerfunct cdr);
  innerfunct m;;



let mutable a = 1::3::4::[]
multiply a
让m相乘=
设rec innerfunct ax=函数
|[]->printfn“%A”m

|(car::cdr)->(在match语句中创建的car符号是不可变的,因此当您与
(car::cdr)
匹配时,您不能更改它们的值

标准的功能方法是生成一个包含计算值的新列表。为此,您可以编写如下内容:

let multiplyBy2 = List.map (fun x -> x * 2)
multiplyBy2 [1;2;3;4;5]
这本身不是尾部递归(但List.map是)。 如果确实要更改列表的值,可以使用数组。这样,函数将不会生成任何新对象,只需在数组中迭代:

let multiplyArrayBy2 arr =
    arr
    |> Array.iteri (fun index value -> arr.[index] <- value * 2)

let someArray = [| 1; 2; 3; 4; 5 |]
multiplyArrayBy2 someArray
let multiplyArrayBy2 arr=
啊
|>Array.iteri(fun index value->arr.[index]F#是一个“单次传递”编译器,因此您可以预期任何编译错误都会在错误下产生级联效应。当您出现编译错误时,请关注该单一错误。虽然您的代码中可能会有更多错误(确实如此)也可能是后续错误只是第一个错误的结果

正如编译器所说,
car
是不可变的,因此可以给它赋值

在函数式编程中,映射可以很容易地实现为递归函数:

// ('a -> 'b) -> 'a list -> 'b list
let rec map f = function
    | [] -> []
    | h::t -> f h :: map f t
然而,这个版本不是尾部递归的,因为它递归地调用
map
,然后将头部转化为尾部

通过引入一个“内部”实现函数,使用累加器对结果进行累加,通常可以重构为尾部递归实现

// ('a -> 'b) -> 'a list -> 'b list
let map' f xs =
    let rec mapImp f acc = function
        | [] -> acc
        | h::t -> mapImp f (acc @ [f h]) t
    mapImp f [] xs
这里,
mapImp
是在
h::t
案例中调用的最后一个操作

此实现效率有点低,因为它在每次迭代中连接两个列表(
acc@[f h]
)。根据要映射的列表的大小,对累加器进行cons,然后在末尾执行单个反转可能更有效:

// ('a -> 'b) -> 'a list -> 'b list
let map'' f xs =
    let rec mapImp f acc = function
        | [] -> acc
        | h::t -> mapImp f (f h :: acc) t
    mapImp f [] xs |> List.rev
然而,无论如何,做这一切的唯一原因是为了练习,因为

在所有情况下,都可以使用映射函数将列表中的所有元素乘以2:

> let mdouble = List.map ((*) 2);;

val mdouble : (int list -> int list)

> mdouble [1..10];;
val it : int list = [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]
不过,通常情况下,我甚至不想显式定义此类函数。相反,您可以内联使用它:

> List.map ((*) 2) [1..10];;
val it : int list = [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]

您可以以相同的方式使用上述所有映射函数。

如果不显示中间代码,我很难解释许多问题,因此我将尝试通过一个带注释的重构:

首先,我们将沿着可变路径前进

  • 由于F#列表是不可变的,而primitive
    int
    s也是不可变的,因此我们需要一种方法来改变列表中的东西:

    let mutable a = [ref 1; ref 3; ref 4]
    
  • 去掉多余的
    ax
    并稍微安排案例,我们可以利用这些参考单元:

    let multiply m =
        let rec innerfunct = function
        | [] -> printfn "%A" m
        | car :: cdr ->
            car := !car*2
            innerfunct cdr
        innerfunct m
    
  • 我们看到,乘法只调用它的内部函数,所以我们得到第一个解:

    let rec multiply m =
        match m with
        | [] -> printfn "%A" m
        | car :: cdr ->
            car := !car*2
            multiply cdr
    
    let multiply m =
        let rec innerfunct = function
        | [] -> []
        | car :: cdr -> car*2 :: innerfunct cdr
        innerfunct m
    
    let a = [1; 3; 4]
    printfn "%A" a
    let multiplied = multiply a
    printfn "%A" multiplied
    
    这实际上只是为了它自己的目的。如果您想要可变,请使用数组和传统for循环

  • 然后,我们沿着不可变的路径前进

  • 正如我们在易变世界中所学到的,第一个错误是由于
    car
    不易变。它只是一个不可变列表中的原始
    int
    。生活在一个不可变的世界中意味着我们只能从我们的输入中创建新的东西。我们想要的是构建一个新列表,以
    car*2
    为头,然后是结果递归调用
    innerfunct
    的t。通常,函数的所有分支都需要返回相同类型的内容:

    let multiply m =
        let rec innerfunct = function
        | [] ->
            printfn "%A" m
            []
        | car :: cdr -> 
            car*2 :: innerfunct cdr
        innerfunct m
    
  • 知道
    m
    是不可变的,我们可以去掉
    printfn
    。如果需要,我们可以将它放在函数之外,任何我们可以访问列表的地方。它将始终打印相同的内容

  • 最后,我们还将对列表的引用设置为不可变,并获得第二个(中间)解决方案:

    let rec multiply m =
        match m with
        | [] -> printfn "%A" m
        | car :: cdr ->
            car := !car*2
            multiply cdr
    
    let multiply m =
        let rec innerfunct = function
        | [] -> []
        | car :: cdr -> car*2 :: innerfunct cdr
        innerfunct m
    
    let a = [1; 3; 4]
    printfn "%A" a
    let multiplied = multiply a
    printfn "%A" multiplied
    
  • 也可以将不同的值相乘(函数名为
    multiply
    ,而不是
    double
    )。此外,既然
    innerfunct
    非常小,我们可以使名称与小范围相匹配(范围越小,名称越短):

    请注意,我将因子放在第一位,列表放在最后。这与其他
    list
    函数类似,允许使用部分应用程序创建预定制函数:

    let double = multiply 2
    let doubled = double a
    
  • 现在剩下的就是使
    乘法
    尾部递归:

    let multiply m xs =
        let rec inner acc = function
        | [] -> acc
        | x :: tail -> inner (x*m :: acc) tail
        inner [] xs |> List.rev
    

  • 因此,我们最终得到了(出于教育目的)一个硬编码版本的
    let multiply'm=List.map((*)m)

    要消除第二个错误,您需要从innerfunct定义中删除
    ax
    参数。
    函数
    语句隐式获取参数。否则,您可以读取整个
    将ax与
    语句匹配,并保持
    ax
    参数不变。