How can I consolidate two function calls into one?

青春壹個敷衍的年華 提交于 2019-12-10 20:02:00

问题


I would like to consolidate the following lines:

let result1 = add (numbers, ",")
let result2 = add (numbers, "\n")

into something like this:

let resultX = add (numbers, ",") |> add (numbers, "\n")

Can I compose functions like this?

NOTE:

I am learning F# and apologize if this question seems silly.

The code is below:

module Calculator

open FsUnit
open NUnit.Framework
open System

let add (numbers:string) =

    let add (numbers:string) (delimiter:string) =
        if (numbers.Contains(delimiter)) then
            numbers.Split(delimiter.Chars(0)) |> Array.map Int32.Parse
                                              |> Array.sum
        else 0

    let result1 = add numbers ","
    let result2 = add numbers "\n"

    if (result1 > 0 || result2 > 0) then 
        result1 + result2

    else let _ , result = numbers |> Int32.TryParse
         result

Tests:

[<Test>]
let ``adding empty string returns zero`` () =

    let result = add ""
    result |> should equal 0

[<Test>]
let ``adding one number returns number`` () =

    let result = add "3"
    result |> should equal 3

[<Test>]
let ``add two numbers`` () =

    let result = add "3,4"
    result |> should equal 7

[<Test>]
let ``add three numbers`` () =

    let result = add "3,4,5"
    result |> should equal 12

[<Test>]
let ``line feeds embedded`` () =

    let result = add "3\n4"
    result |> should equal 7

UPDATED

I receive the following error:

The type 'int' does not match the type 'string'

let add (numbers:string) =

    let add (numbers:string) (delimiter:string) =
        if (numbers.Contains(delimiter)) then
            numbers.Split(delimiter.Chars(0)) |> Array.map Int32.Parse
                                              |> Array.sum
        else 0

let resultX = numbers |> add ","
                      |> add "\n"

Implemented Feedback:

let add (numbers:string) =

    let add (numbers:string) (delimiters:char array) =
        if numbers.Length = 0 then 0
        else numbers.Split(delimiters) |> Array.map Int32.Parse
                                       |> Array.sum
    let delimiters = [|',';'\n'|]
    add numbers delimiters

回答1:


This is not an exact answer as I am not sure what you mean but it should give you some ideas.

let add01 (numbers:string) =
    let delimiters : char array = [|',';'\n'|]
    let inputArray : string array = numbers.Split(delimiters)
    let numbers : string list = Array.toList(inputArray)
    let rec add (numbers : string list) (total : int) : int =
        match (numbers : string list) with
        | ""::t ->
            add t total
        | h::t -> 
            let number =  System.Int32.Parse h
            let total = total + number
            add t total
        | [] -> total        
    add numbers 0

let numbers = "1,2,3\n4,5,6\n\n"
let result = add01 numbers

When given the following code the following error occurs, why?

// Type mismatch. Expecting a
//     int -> 'a    
// but given a
//     string -> int    
// The type 'int' does not match the type 'string'
let result = numbers |> add ","
                     |> add "\n"

Since this is an error stating that two types do not agree one needs to understand type inferencing and how to resolve such problems. I will not explain type inferencing here as that is a large topic in itself, however I will give an example of a pattern that works successfully most of time for me in resolving such errors.

When F# compiles code it uses type inferencing to add the missing types to functions and values before doing a type check and it is the type check that is failing. So to see what the compiler sees for the types we will manually add them here and factor out the parts of the code that are not causing a problem leaving us with the cause of the error hopefully in something then becomes obvious to fix.

The only things that have types are:

  • result
  • =
  • numbers
  • |>
  • add
  • ","
  • "\n"

The types for the values are easy:

  • result : int
  • numbers : string
  • "," : string
  • "\n" : string

I don't recall F# treating equals (=) as a function but here is how to think of it.
= : 'a -> 'a

The pipeline operator

let (|>) (x : 'a) f = f (x : 'a)  

For resolving the problem just think of the pipeline operator as syntactic sugar.
See examples below for better understanding.

The add function
add : string -> string -> int

So lets refine the error down to its essence.

//Type mismatch. Expecting a
//    int -> 'a    
//but given a
//    string -> int    
//The type 'int' does not match the type 'string'
let result = numbers |> add ","
                     |> add "\n"

Add the type signatures to the values and verify we get the same error. This is what type inferencing would do and we did it manually.

//Type mismatch. Expecting a
//    int -> int    
//but given a
//    string -> int    
//The type 'int' does not match the type 'string'
let (result : int) = (numbers : string) |> add ("," : string) 
                     |> add ("\n" : string)

Now think of the code as a mathematical expression which can be factored.

Factor out the first pipeline operator and verify we get the same error. Notice the error is now only part of r2

//Expecting a
//    int -> 'a    
//but given a
//    string -> int    
//The type 'int' does not match the type 'string'
let (result : int) = 
    let r1 = (numbers : string) |> add ("," : string) 
    let r2 = r1 |> add ("\n" : string)
    r2

Undo the syntactic sugar for the second pipeline operator and verify we get the same error. Notice the error is now only part of r2; specifically the r1 argument

//This expression was expected to have type
//    string    
//but here has type
//    int
let (result : int) = 
    let r1 = (numbers : string) |> add ("," : string) 
    let r2 = add ("\n" : string) r1
    r2

Add the type to r1 and verify we get the same error.

//This expression was expected to have type
//    string    
//but here has type
//    int   
let (result : int) = 
    let (r1 : int) = (numbers : string) |> add ("," : string) 
    let r2 = add ("\n" : string) r1
    r2

At this point the error should be obvious. The result of the first pipeline operator is an int and is passed to the add function as the second argument. The add function expects a string for the second argument but was given an int.


To better understand how the pipeline operator works I created an equivalent user defined operator for this demonstration.

These are some helper functions for the demonstration.

let output1 w =
    printfn "1: %A" w

let output2 w x =
    printfn "1: %A 2: %A" w x

let output3 w x y =
    printfn "1: %A 2: %A 3: %A" w x y

let output4 w x y z =
    printfn "1: %A 2: %A 3: %A 4: %A" w x y z

Using the output functions without the pipeline operator.

output1 "a"  
1: "a"  

output2 "a" "b"  
1: "a" 2: "b"  

output3 "a" "b" "c"  
1: "a" 2: "b" 3: "c"  

output4 "a" "b" "c" "d"  
1: "a" 2: "b" 3: "c" 4: "d"  

Notice that the output is in the same order as the input.

Using the output functions with the pipeline operator.

// let (|>) x f = fx

"a" |> output1  
1: "a"  

"a" |> output2 "b"  
1: "b" 2: "a"  

"a" |> output3 "b" "c"  
1: "b" 2: "c" 3: "a"  

"a" |> output4 "b" "c" "d"  
1: "b" 2: "c" 3: "d" 4: "a"  

NOTICE that the last argument for the output functions is the value on the left of the pipeline operator ("a") because of the use of the pipeline operator (|>).

// See section 3.7 of the F# specification on how to define user defined operators.

Using the output functions with the user defined pipeline operator.

let (@.) x f = f x  

"a" @. output1  
1: "a"  

"a" @. output2 "b"  
1: "b" 2: "a"  

"a" @. output3 "b" "c"  
1: "b" 2: "c" 3: "a"  

"a" @. output4 "b" "c" "d"  
1: "b" 2: "c" 3: "d" 4: "a"  



回答2:


I'm not aware of any universal way to compose functions like you seem to be asking, but if you only need to vary one argument, one option is to create a list of arguments, and then map over those:

let results = [","; "\n"] |> List.map (add numbers)

If you do this, then results is an int list, and then you need to decide what to do with that list. In this case, it would seem appropriate to sum over the list, but given the current conditionals that check if result1 or result2 are positive, that doesn't seem appropriate.


All that said, given the current test cases supplied, there's no reason to make it more complicated than it has to be. This implementation also passes all the tests:

let add =
    let split (x : string) =
        x.Split([| ','; '\n' |], StringSplitOptions.RemoveEmptyEntries)
    split >> Array.map Int32.Parse >> Array.sum

This isn't a particularly robust implementation, as it'll fail if the string contains characters that can't be parsed into integers, but so will the OP implementation.



来源:https://stackoverflow.com/questions/34655981/how-can-i-consolidate-two-function-calls-into-one

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!