38

I am a bit confused by the Literal keyword and why it is necessary in F#.

Reading the docs, it sounds to me that [<Literal>] is used to define a constant, however I am a bit confused how this constant differs from all other constants in F#..

Values that are intended to be constants can be marked with the Literal attribute. This attribute has the effect of causing a value to be compiled as a constant.

When I think of a constant, I think of something which is immutable....

let x = "a" + "b" //this is a immutable value, its value is constant
[<Literal>]
let y = "a" + "b" //this is also a immutable value, but why is this a special constant?

Is it because the 'normal' F# values are evaluated lazily and the [<Literal>] is not evaluated lazily..? is that what they mean with 'compiled as constant'..? or is there something else to it?

Michelrandahl
  • 3,365
  • 2
  • 26
  • 41
  • I found this question because I was also confused by that paragraph in the language reference, for the same reason, ie. every 'let' bound value in F# is "constant" so what's special about a `Literal`? The word "constant" does not have a particular meaning in F# which might explain this para. The answers here explained it well, but I have raised a github "issue" on the paragraph. https://github.com/dotnet/docs/issues/32082. – Stephen Hosking Oct 29 '22 at 01:35

4 Answers4

53

In your example, x is an immutable value that is assigned during runtime (but NOT evaluated lazily), whereas y is assigned during compiletime. As an example,

let myDLL = "foo.dll"

[<DllImport(myDLL, CallingConvention = CallingConvention.Cdecl)>]
extern void HelloWorld()

will not work, because DllImport is an attribute and needs to know the value of myDLL during compilation. However, this will work:

[<Literal>]
let myDLL = "foo.dll"

[<DllImport(myDLL, CallingConvention = CallingConvention.Cdecl)>]
extern void HelloWorld()
John Reynolds
  • 4,927
  • 4
  • 34
  • 42
  • 4
    You might also want to note the importance for .NET interop. Fields decorated `[]` are compiled into IL as constants, which has certain implications for consumers of your code. See http://stackoverflow.com/a/755693/385844, for example. – phoog Sep 08 '14 at 20:56
  • 3
    Please note that according to f# naming conventions, literals should be PascalCase. If you use them for pattern matching, this is more than just convention: https://learn.microsoft.com/sv-se/dotnet/fsharp/language-reference/literals – Guran Sep 18 '18 at 08:19
30

If you come from C# background, you can think of Literal values as const fields, and non-literal ones as readonly fields. The same differences apply.

cynic
  • 5,305
  • 1
  • 24
  • 40
15

I think a better example is what happens in a match.

This doesn't do what you expect:

let t = 3
match q with
|t -> printfn "this always happens"
|_ -> printfn "this never happens" //and produces a compiler warning

on the other hand:

[<Literal>]
let t = 3
match q with
|t -> printfn "q is 3"
|_ -> printfn "q isn't 3"

So here as the Literal is a compile time constant we can use it for pattern matching.

John Palmer
  • 25,356
  • 3
  • 48
  • 67
  • 5
    Unfortunately, this example is case sensitive. To work as described the literal identifier `t` must begin with a capital letter. Using a capital for the non-literal version produces an additional compiler warning, but functions identically. – cadull Feb 23 '15 at 03:56
  • The example actually works, and produces "q is 3", but that's because `t` without a `when` clause matches every value. It's not the same `t` assigned in `let t = 3`. If you replace `|t` with `z` you get the same runtime result, but with `|t` you also get a compiler warning `FS3190: Lowercase literal 't' is being shadowed by a new pattern with the same name. Only uppercase and module-prefixed literals can be used as named patterns.` – Stephen Hosking Oct 28 '22 at 22:44
  • 1
    I strongly recommend the syntax `let [] T = 3` in order to avoid _code drift_ - that is, the attribute and the code element to which it applies easily drifting away from each other due to later edits. – Bent Tranberg Oct 29 '22 at 07:30
0

The docs with that paragraph (Literals) briefly refer to "pattern matching", the when clause, and the use of PascalCase identifiers, but need an example for clarity. Unfortunately, the above example of pattern matching is incorrect (by using lowercase t for the literal).

[<Literal>]
let X = 3   // PascalCase by convention for a literal

match 3 with 
| X -> "Pattern Matched with X" // PascalCase REQUIRED for pattern matching with a constant.
| x when x = X -> "Matched with X, via 'when'" // lower case is always a local identifier
| _ -> "false"

// RESULT 'val it : string = "Pattern Matched with X"'
Stephen Hosking
  • 1,405
  • 16
  • 34