问题
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