Luca Fülbier
Luca Fülbier

Reputation: 2731

Forcing let to bind to a function instead of a value

I have this piece of code that is supposed to generate random names:

let generateName =
    let rnd = System.Random()
    let c1 = rnd.Next(65, 90) |> System.Convert.ToChar |> string
    let c2 = rnd.Next(65, 90) |> System.Convert.ToChar |> string
    let n = rnd.Next(0, 999)  |> string
    c1 + c2 + n

This evaluates to a string, which isn't quite what i want. Adding a unit-Parameter fixes the problem, but now i have to call my function like a C# function:

let myFunc() =
    "Hello"

myFunc()

Is this how i have to do it, or is there a more idiomatic way?

Upvotes: 5

Views: 754

Answers (3)

ildjarn
ildjarn

Reputation: 62995

TL;DR: () in this context has an entirely different meaning than it has in C#, so do not format it the same way as in C# – use let myFunc () = and myFunc () instead, which better reflects actual semantics and will in turn clarify how you think about and understand the code.


In C#, () means an empty set of parameters (for function/constructor definitions) or arguments (for function/constructor call-sites); i.e., it defines or invokes a nullary function. In F#, () is a literal of type unit, akin to how 42 is a literal of type int; i.e., it is a value in and of itself, just as [] is a value for an empty list. This difference has lots of little implications.

In F#, when you define a let-function with only () for parameters, as in let myFunc() =, you are actually using pattern-matching syntax to define a unary function with a parameter of type unit (not a nullary function!). This particular instance of pattern-matching is so taken for granted that many people do not realize that it's pattern-matching in the first place; but, in fact, defining a function as let myFunc () = rather than as let myFunc (_:unit) = is akin to having let myFunc [] = rather than let myFunc (x:'a list) = (and subsequently asserting that x is empty).

And in F#, when you invoke a let-function with (), as in myFunc (), you are not denoting 'no arguments'; you are in fact supplying an argument of type unit, whose singular value is so often used that it has its own literal syntax: ().

To convince yourself unit is different from 'no parameters/arguments', and is in fact just another parameter/argument like any other, consider the fact that the following is completely legal:

let myFunc() (i:int) (_:unit*unit) = i
let n = myFunc() 42((),())
printfn "%d" n

As you can see, giving unit special whitespace treatment is silly given that it's not special. ;-]

Summarily, since it is usual (necessary?) to separate any named parameter/argument with whitespace, it is then idiomatic to be consistent and do the same with symbolic patterns/values:

let myFunc () =
    "Hello"

myFunc ()

Upvotes: 7

kemiller2002
kemiller2002

Reputation: 115538

Nope, that's how F# works. Every function in F# must have one parameter (functions with multiple parameters are split into multiple functions with one parameter returning an intermediate function), and every function must return something, even if it's unit.

Upvotes: 6

TeaDrivenDev
TeaDrivenDev

Reputation: 6629

This is exactly how it works. A function with no "useful" parameters needs to have a unit parameter to actually make it a function.

It is not uncommon to separate the function name and () by a space to make it look a bit less method-y and more function-y, but that's purely cosmetic.

Upvotes: 5

Related Questions