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.
actionOne. Adding type annotations doesn't change whether a function is curryable or not. On your last line, the parens aren't necessary. - Abel