40

From Operators.id<'T> Function (F#):

The identity function.

Parameters: x Type: 'T (The input value)

Return Value: The same value

F# Core Library Versions, supported in: 2.0, 4.0, Portable

Why is there a function that returns its input?

adelarsq
  • 3,718
  • 4
  • 37
  • 47
MiP
  • 5,846
  • 3
  • 26
  • 41
  • 7
    Interesting to note the mathematical background as well. Having a 'do nothing' object gives important structure. As zero is an identity for numbers under addition, and one is an identity for numbers under multiplication, so `id` is an identity for the set of functions under composition. And in functional languages you'll often want to manipulate functions as objects, perform operations over them etc. – matt_t_gregg Jun 16 '17 at 05:42
  • @matt_t_gregg if you post that as an answer, I'm sure you'll get quite a few upvotes. – Fyodor Soikin Jun 18 '17 at 21:00
  • @FyodorSoikin I was pretty happy that your answer covered the practicalities of why it was needed and used - the math thing I thought was more an interesting aside. – matt_t_gregg Jun 18 '17 at 23:30

3 Answers3

44

When working with higher-order functions (i.e. functions that return other functions and/or take other functions as parameters), you always have to provide something as parameter, but there isn't always an actual data transformation that you'd want to apply.

For example, the function Seq.collect flattens a sequence of sequences, and takes a function that returns the "nested" sequence for each element of the "outer" sequence. For example, this is how you might get the list of all grandchildren of a UI control of some sort:

let control = ...
let allGrandChildren = control.Children |> Seq.collect (fun c -> c.Children)

But a lot of times, each element of the sequence will already be a sequence by itself - for example, you may have a list of lists:

let l = [ [1;2]; [3;4]; [5;6] ]

In this case, the parameter function that you pass to Seq.collect needs to just return the argument:

let flattened = [ [1;2]; [3;4]; [5;6] ] |> Seq.collect (fun x -> x)

This expression fun x -> x is a function that just returns its argument, also known as "identity function".

let flattened = [ [1;2]; [3;4]; [5;6] ] |> Seq.collect id

Its usage crops up so often when working with higher-order functions (such as Seq.collect above) that it deserves a place in the standard library.

Another compelling example is Seq.choose - a function that filters a sequence of Option values and unwraps them at the same time. For example, this is how you might parse all strings as numbers and discard those that can't be parsed:

let tryParse s = match System.Int32.TryParse s with | true, x -> Some x | _ -> None
let strings = [ "1"; "2"; "foo"; "42" ]
let numbers = strings |> Seq.choose tryParse  // numbers = [1;2;42]

But what if you're already given a list of Option values to start with? The identity function to the rescue!

let toNumbers optionNumbers =
   optionNumbers |> Seq.choose id
Fyodor Soikin
  • 78,590
  • 9
  • 125
  • 172
  • 5
    The `Seq.choose id` example is a great one, but `Seq.collect id` is just a less readable way of saying `Seq.concat`. (That said, I'm pretty sure I've written `Seq.collect id` a number of times too and it illustrates what `id` does well!) – Tomas Petricek Jun 15 '17 at 10:48
  • @TomasPetricek often one's train of thought leads to `Seq.collect id`. Mine usually fails to take the next step to `Seq.concat`, though your comment will probably increase the chance of this happening next time. Anyway, it's nice to have the flexibility. I sometimes wish there were an `Id()` function in C#, and I've hand-rolled it on occasion, but the difference in type inference makes it slightly less useful. – phoog Jan 15 '22 at 09:28
17

It's useful for certain higher order functions (functions that take functions as arguments) so that you can pass id as the argument instead of writing out the lambda (fun x -> x).

[[1;2]; [3]] |> List.collect id  // [1; 2; 3]
TheQuickBrownFox
  • 10,544
  • 1
  • 22
  • 35
1

It can be extremely useful when working with options.

I have written a small idiomatic JSON-Helper, indicating all optional fields as Option, throwing errors, if a string is passed as null, if not of type 'string option'.

Now there is a function providing a boxed output value, which can be

  1. 'a -> any type but no option
  2. 'b -> 'x option

In order to box the value correctly, I use

val |> if isOption then fnOptTransform else id

So I'm applying the high order function fnOptTransform, and by calling id otherwise, avoid the ugliness of coding a separate lambda (I try to avoid it, where I can..). Found it useful.

Coderookie
  • 51
  • 2