简体   繁体   中英

Tail Recursive map f#

I want to write a tail recursive function to multiply all the values in a list by 2 in F#. I know there is a bunch of ways to do this but i want to know if this is even a viable method. This is purely for educational purposes. I realize that there is a built in function to do this for me.

 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

I get two errors with this though i doubt they are the only problems.

This value is not mutable on my second matching condition

and

This expression is a function value, ie is missing arguments. Its type is 'a list -> unit. for when i call length a .

I am fairly new to F# and realize im probably not calling the function properly but i cant figure out why. This is mostly a learning experience for me so the explanation is more important than just fixing the code. The syntax is clearly off, but can i map *2 to a list just by doing the equivalent of

car = car*2 and then calling the inner function on the cdr of the list.

There are a number of issues that I can't easily explain without showing intermediate code, so I'll try to walk through a commented refactoring:

First, we'll go down the mutable path :

  1. As F# lists are immutable and so are primitive int s, we need a way to mutate that thing inside the list:

     let mutable a = [ref 1; ref 3; ref 4] 
  2. Getting rid of the superfluous ax and arranging the cases a bit, we can make use of these reference cells:

     let multiply m = let rec innerfunct = function | [] -> printfn "%A" m | car :: cdr -> car := !car*2 innerfunct cdr innerfunct m 
  3. We see, that multiply only calls its inner function, so we end up with the first solution:

     let rec multiply m = match m with | [] -> printfn "%A" m | car :: cdr -> car := !car*2 multiply cdr 

    This is really only for it's own purpose. If you want mutability, use arrays and traditional for-loops.

Then, we go up the immutable path :

  1. As we learnt in the mutable world, the first error is due to car not being mutable. It is just a primitive int out of an immutable list. Living in an immutable world means we can only create something new out of our input. What we want is to construct a new list, having car*2 as head and then the result of the recursive call to innerfunct . As usual, all branches of a function need to return some thing of the same type:

     let multiply m = let rec innerfunct = function | [] -> printfn "%A" m [] | car :: cdr -> car*2 :: innerfunct cdr innerfunct m 
  2. Knowing m is immutable, we can get rid of the printfn . If needed, we can put it outside of the function, anywhere we have access to the list. It will always print the same.

  3. We finish by also making the reference to the list immutable and obtain a second (intermediate) solution:

     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 
  4. It might be nice to also multiply by different values (the function is called multiply after all and not double ). Also, now that innerfunct is so small, we can make the names match the small scope (the smaller the scope, the shorter the names):

     let multiply m xs = let rec inner = function | [] -> [] | x :: tail -> x*m :: inner tail inner xs 

    Note that I put the factor first and the list last. This is similar to other List functions and allows to create pre-customized functions by using partial application:

     let double = multiply 2 let doubled = double a 
  5. All that's left now is to make multiply tail-recursive:

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

So we end up having (for educational purposes) a hard-coded version of let multiply' m = List.map ((*) m)

F# is a 'single-pass' compiler, so you can expect any compilation error to have a cascading effect beneath the error. When you have a compilation error, focus on that single error. While you may have more errors in your code (you do), it may also be that subsequent errors are only consequences of the first error.

As the compiler says, car isn't mutable, so you can assign a value to it.

In Functional Programming, a map can easily be implemented as a recursive function:

// ('a -> 'b) -> 'a list -> 'b list
let rec map f = function
    | [] -> []
    | h::t -> f h :: map f t

This version, however, isn't tail-recursive, since it recursively calls map before it cons the head onto the tail.

You can normally refactor to a tail-recursive implementation by introducing an 'inner' implementation function that uses an accumulator for the result. Here's one way to do that:

// ('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

Here, mapImp is the last operation to be invoked in the h::t case.

This implementation is a bit inefficient because it concatenates two lists ( acc @ [fh] ) in each iteration. Depending on the size of the lists to map, it may be more efficient to cons the accumulator and then do a single reverse at the end:

// ('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

In any case, however, the only reason to do all of this is for the exercise, because this function is already built-in .

In all cases, you can use map functions to multiply all elements in a list by two:

> 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]

Normally, though, I wouldn't even care to define such function explicitly. Instead, you use it inline:

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

You can use all the above map function in the same way.

Symbols that you are creating in a match statement are not mutable, so when you are matching with (car::cdr) you cannot change their values.

Standard functional way would be to produce a new list with the computed values. For that you can write something like this:

let multiplyBy2 = List.map (fun x -> x * 2)
multiplyBy2 [1;2;3;4;5]

This is not tail recursive by itself (but List.map is). If you really want to change values of the list, you could use an array instead. Then your function will not produce any new objects, just iterate through the array:

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

let someArray = [| 1; 2; 3; 4; 5 |]
multiplyArrayBy2 someArray

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM