do `fun` lambda expressions have shorthand syntax?

Viewed 157

i want to make sure that i am not missing out on readable-but-terse F# syntax: is my use of fun below too verbose?

let toCamelCase word indexes =
    let mapping i c =
        match (indexes |> List.contains i) with
        | true                      -> Char.ToUpper(c)
        | _ when Char.IsUpper(c)    -> Char.ToLower(c)
        | _                         -> c

    word |> String.mapi mapping

[
    ("fsharP", [0; 1])
    ("nAtiveinterop", [0; 6])
    ("taskbuildereXtensions", [0; 4; 11])
    ("microsoftword", [0; 9])
]
|> List.map (fun (word, indexes) -> (word, indexes) ||> toCamelCase)

also, do let me know whether there can be improvements elsewhere in the code above

4 Answers

In some functional languages, an uncurry function is pretty common:

let uncurry f (a,b) = f a b

Then you can write |> List.map (uncurry toCamelCase) instead.

Alternatively, you can just simplify what you have now a little to:

|> List.map (fun pair -> pair ||> toCamelCase)

Wanted to add an answer that doesn't focus on currying/uncurrying as much. This touches on some points brought up by others here already, but hopefully some more details will be helpful.

Regarding your question, you need the fun keyword if you want to pass in a lambda function. You could avoid this by changing your function signature:

let toCamelCase (word, indexes) =
    let mapping i c =
        match (indexes |> List.contains i) with
        | true                      -> Char.ToUpper(c)
        | _ when Char.IsUpper(c)    -> Char.ToLower(c)
        | _                         -> c

    word |> String.mapi mapping

[
    ("fsharP", [0; 1])
    ("nAtiveinterop", [0; 6])
    ("taskbuildereXtensions", [0; 4; 11])
    ("microsoftword", [0; 9])
]
|> List.map toCamelCase

If you were using a function you couldn't make this change to for whatever reason you could create an intermediate helper function (this is effectively manual uncurrying):

let toCamelCase word indexes =
    let mapping i c =
        match (indexes |> List.contains i) with
        | true                      -> Char.ToUpper(c)
        | _ when Char.IsUpper(c)    -> Char.ToLower(c)
        | _                         -> c

    word |> String.mapi mapping

let toCCHelper (word, indexes) =
    toCamelCase word indexes

[
    ("fsharP", [0; 1])
    ("nAtiveinterop", [0; 6])
    ("taskbuildereXtensions", [0; 4; 11])
    ("microsoftword", [0; 9])
]
|> List.map toCCHelper

You could choose to simplify your lambda function slightly and change nothing else. This works because the double pipe (||>) will deconstruct the tuple input for you:

let toCamelCase word indexes =
    let mapping i c =
        match (indexes |> List.contains i) with
        | true                      -> Char.ToUpper(c)
        | _ when Char.IsUpper(c)    -> Char.ToLower(c)
        | _                         -> c

    word |> String.mapi mapping

[
    ("fsharP", [0; 1])
    ("nAtiveinterop", [0; 6])
    ("taskbuildereXtensions", [0; 4; 11])
    ("microsoftword", [0; 9])
]
|> List.map (fun x -> x ||> toCamelCase)

There are also some parenthesis that are not needed, so it is up to your preferences. Here's some cleanup there as well as some logic changes for your consideration:

let toCamelCase (word, indexes) =
    let mapping i c =
        // You can omit parens here  if you want:
        match indexes |> List.contains i with
        // This logic might be easier to maintain, no parens needed here:
        | true  -> Char.ToUpper c
        | false -> Char.ToLower c

    word |> String.mapi mapping

// The parens here are also optional when you're putting 1 entry per line
// (1 tuple being 1 entry in this case):
[
    "fsharP", [0; 1]
    "nAtiveinterop", [0; 6]
    "taskbuildereXtensions", [0; 4; 11]
    "microsoftword", [0; 9]
]
|> List.map toCamelCase

Here are my versions that i came up with. I would prefer xs4 and just do everything inline, without map at all. If the function has a very long name, maybe xs5.

On top, for readability, i would add spaces to create a tabular view. But this is maybe an old habit of an old Perl programmer who cares for readability, and is not far used in any other languages. Maybe all the other languages think they are already readable?

(* Original *)
let xs1 =
    [
        "fsharP"               , [0; 1]
        "nAtiveinterop"        , [0; 6]
        "taskbuildereXtensions", [0; 4; 11]
        "microsoftword"        , [0; 9]
    ]
    |> List.map (fun (word, indexes) -> (word, indexes) ||> toCamelCase)

(* Remove useless piping *)
let xs2 =
    [
        "fsharP"               , [0; 1]
        "nAtiveinterop"        , [0; 6]
        "taskbuildereXtensions", [0; 4; 11]
        "microsoftword"        , [0; 9]
    ]
    |> List.map (fun (word, indexes) -> toCamelCase word indexes)

(* If you use piping, then like this *)
let xs3 =
    [
        "fsharP"               , [0; 1]
        "nAtiveinterop"        , [0; 6]
        "taskbuildereXtensions", [0; 4; 11]
        "microsoftword"        , [0; 9]
    ]
    |> List.map (fun wi -> wi ||> toCamelCase)

(* toCamelCase part of list *)
let xs4 = [
    toCamelCase "fsharP"                [0; 1]
    toCamelCase "nAtiveinterop"         [0; 6]
    toCamelCase "taskbuildereXtensions" [0; 4; 11]
    toCamelCase "microsoftword"         [0; 9]
]

(* you can create a shortcut for the function *)
let xs5 =
    let f = toCamelCase
    [
        f "fsharP"                [0; 1]
        f "nAtiveinterop"         [0; 6]
        f "taskbuildereXtensions" [0; 4; 11]
        f "microsoftword"         [0; 9]
    ]

(* Use map2 *)
let xs6 =
    List.map2
        toCamelCase
        ["fsharP";"nAtiveinterop";"taskbuildereXtensions";"microsoftword"]
        [[0;1];   [0;6];          [0;4;11];               [0;9]]

(* still map2, but extracted arguments *)
let xs7 =
    let args1 = ["fsharP";"nAtiveinterop";"taskbuildereXtensions";"microsoftword"]
    let args2 = [[0;1];   [0;6];          [0;4;11];               [0;9]]
    List.map2 toCamelCase args1 args2

You can use currying to apply function without explicitly declaring lambda:

let inline flip f x y = f y x
...
List.map (flip (||>) toCamelCase)

OR change order of parameters in toCamelCase or in input list, this way you won't need to define flip:

List.map ((||>) toCamelCase)

But I think that best option is to just avoid ||>:

List.map (fun (word, indexes) -> toCamelCase word indexes)
Related