1
votes
let actionOne value =
    sprintf "action one %s" (value.Substring(1))

let actionTwo (value:string) =
    sprintf "action two: %s" (value.Substring(1))


let one = actionOne "one"
let two = actionTwo("two")

How can I maintain the Curried form AND add the type annotation, value:string, on the actionOne function ?

[Edit]
MS documentation shows this example to compare "Tuple form" and "Curried form":

// Tuple form.
member this.SomeMethod(param1, param2) = ...
// Curried form.
let function1 param1 param2 = ...

My code does not compile because value needs a type annotation (since it is used with .Substring() ).
I can add the type annotation, but that makes ActionOne signature identical to ActionTwo.
This change FORCE me to use the parenthesis when I want to use it:

How can I change the ActionOne function (adding type annotation) and avoid to change the call, that is: remaining without parenthesis ?

1
actually, actionTwo still is in Curried form - Nghia Bui
I don't really understand the question, the type annotation is necessary on actionOne. Adding type annotations doesn't change whether a function is curryable or not. On your last line, the parens aren't necessary. - Abel

1 Answers

1
votes

As the comments by me and Bui suggest, both actionOne and actionTwo are in curried form. But since there's only one parameter, there's no distinction between curried form and non-curried form: another way to say this is that the argument is a one-tuple, the most basic of tuples.

If you have two parameters or more, it becomes more relevant. When you tuple the arguments, it is in tupled form, and when you don't the function will be curryable.

Here's how to assign, or not, the type annotation:

/// curried form
let f x y = printfn "%A, %A" x y
/// tupled argument
let f (x, y) = printfn "%A, %A" x y

/// curried form with type annotations
let f (x: string) (y: string) = printfn "%A, %A" x y
/// tupled argument with type annotations
let f (x: string, y: string) = printfn "%A, %A" x y

/// curried form with type annotations and return type spec
let f (x: string) (y: string): unit = printfn "%A, %A" x y
/// tupled argument with type annotations and return type spec
let f (x: string, y: string): unit = printfn "%A, %A" x y

When we paste these lines one by one in FSI you can see the signature change (except for the last two, as the return type was already inferred to be unit anyway):

> let f x y = printfn "%A, %A" x y;;
val f : x:'a -> y:'b -> unit

> let f (x, y) = printfn "%A, %A" x y;;
val f : x:'a * y:'b -> unit

> let f (x: string) (y: string) = printfn "%A, %A" x y;;
val f : x:string -> y:string -> unit

> let f (x: string, y: string) = printfn "%A, %A" x y;;
val f : x:string * y:string -> unit

> let f (x: string) (y: string): unit = printfn "%A, %A" x y;;
val f : x:string -> y:string -> unit

> let f (x: string, y: string): unit = printfn "%A, %A" x y;;
val f : x:string * y:string -> unit

An alternative for the tupled form is to have a single argument that is the tuple, this can sometimes come in handy. You declare it like this, and you can call it just the same as any other function that takes a tupled argument: either with a variable that is a tuple already, or with two arguments in tuple form:

// let type inference do the work for you:
> let f x = printfn "%A, %A" (fst x) (snd x);;
val f : 'a * 'b -> unit

// with specific type
> let f (x: string * string) = printfn "%A, %A" (fst x) (snd x);;
val f : string * string -> unit

Response to your edit in your Q.:

I can add the type annotation, but that makes ActionOne signature identical to ActionTwo. This change FORCE me to use the parenthesis when I want to use it:

There seems to be some confusion about the use of parens. The example that the MS docs give suggest "no parens means no tuples". That is not entirely true:

// no parens, curried
let f a b = a + b
let x = f 42 43

// with parens, this is identical and still curried
let f (a) (b) = a + b
let x = f (42) (43)

// with parens in other places, still identical, except for the type
let f (a) (b) = (a: int64) + (b: int64)
let x = f (42L: int64) (43L)

The reason the MS docs showed parens is that you cannot write f x, y = x + y. In this case, you must use parens. This has everything to do with the space character having the highest precedence in virtually all cases in F#. The comma has lowest. Hence you must give parenthesis to tell the compiler that you want the arguments to be tupled: f (x, y) = x + y.

In the case of the single-argument function, you need parens with type annotations because otherwise you would specify the return type. So f x: int = x + 12 is a function that has specified a return type, and f (x: int) = x + 12 is a function that has specified the type of argument x.