cmeeren
cmeeren

Reputation: 4210

Caveats of returning anonymous records instead of tuples

Consider the following (general, could be publicly available in a library) utility function for parsing a string as HTTP Basic credentials and returning the username and password:

let parseBasicCredentials (encodedCredentials: string) =
  (* approx. 10 lines of pipes, matching and try/with *)
  username, password

Since both the username and password are strings, callers need to remember (or check the docs) that the username is returned as the first element of the tuple, and the password as the second.

However, F# will soon get support for anonymous records (already available in nightlies). When that happens, such functions can also return an anonymous record:

let parseBasicCredentials (encodedCredentials: string) =
  (* approx. 10 lines of pipes, matching and try/with *)
  {| Username = username; Password = password |}

This seems to work fine, but since this is a brand new language feature, I'm unsure if there are any downsides to this, whether technical or simply frowned upon. AFAIK returning a tuple is an idiomatic and accepted solution for simple helper functions such as this. An anonymous type additionally allows you to name the elements and can thus be considered more "safe" since it's more self-documenting and thus better at preventing the caller mixing up the returned values.

I do not consider general-purpose (non-domain) helper functions such as this candidates for returning separately defined record types, or tuples of single-case DUs. I am only curious, now that anonymous records exist, whether they can serve as "better tuples" when one needs ad-hoc, non-primitive return values like here, or if tuples are somehow still preferred (and, if so, for which reasons).

Upvotes: 4

Views: 118

Answers (1)

TheQuickBrownFox
TheQuickBrownFox

Reputation: 10624

I haven't used anonymous records much but I believe the main limitation is that you can't write functions that take them as inputs. This severely limits their use as application-wide data to be passed between functions, and that's an intentional design choice.

They will be more useful as a convenient way to store transient data within a function or a script file.

Correction:

It is possible to take them as inputs. Here's an example:

let showCredentials (x:{| Username:string; Password:string |}) =
    printfn "Username: %s, Password %s" x.Username x.Password

However, you must mention all of the fields in the record for it to match on the type. So to write another function that accepts the same type but doesn't use the password you would write this:

let showUsername (x:{| Username:string; Password:_ |}) =
    printfn "Username: %s" x.Username

This means every time you edit the type definition at all then you'll have to edit all the type annotations for function parameters of the type. That doesn't scale very well for general use.

It's possible that pattern matching will be added so you could instead write:

let showUsername {| Username = username |} =
    printfn "Username: %s" username

But it's not clear to me if this is going to be added to the language.

Upvotes: 2

Related Questions