42

When thinking in a functional mindset, given that functions are supposed to be pure, one can conclude any function with no arguments is basically just a value.
However, reallity gets in the way, and with different inputs, I might not need a certain function, and if that function is computationally expensive, I'd like to not evaluate it if it's not needed.
I found a workaround, using let func _ = ... and calling it with func 1 or whatever, but that feels very non-idiomatic and confusing to the reader.

This boils down to one question: In F#, Is there a proper way to declare a function with zero arguments, without having it evaluated on declaration?

Guy Coder
  • 22,011
  • 6
  • 54
  • 113
Rubys
  • 3,057
  • 2
  • 24
  • 24
  • One might also take the stance that values are "basically functions with no arguments," which is how Haskell views things. I wonder why F# is different in that regard. – jpaugh Aug 28 '17 at 15:40
  • @self It's because Haskell is also lazy by default; no values, including functions, are evaluated on declaration unless the programmer explicitly asks for it. The semantics in F# are strict by default, and should be represented differently. – jpaugh Aug 28 '17 at 15:49

2 Answers2

62

The usual idiom is to define the function to take one argument of type Unit (let functionName () = 42). It will then be called as functionName (). (The unit type has only one value, which is ().)

jpaugh
  • 5,719
  • 4
  • 33
  • 83
sepp2k
  • 341,501
  • 49
  • 643
  • 658
  • hmm but you can't initialize a record type with this syntax (e.g. type Getter = {Name: string; Function: () -> string;} is not allowed). Any idea how to get around this? – evolvedmicrobe Apr 04 '14 at 21:29
  • thanks for response, but it doesn't compile. Is it possible to write a record type like this? – evolvedmicrobe Apr 04 '14 at 22:21
  • @evolvedmicrobe It compiles fine for me with `unit`. – sepp2k Apr 04 '14 at 22:41
  • 1
    [Joel Mueller's answer](http://stackoverflow.com/a/2798581/177710) addresses the two main points of the question in an even better way: **any function with no arguments is basically just a value** and **I'd like to not evaluate it if it's not needed** are both addressed very well by `lazy()`. – Oliver Feb 09 '16 at 08:29
  • @Oliver, actually, using `lazy()` will have different memory/performance characteristics, and may not always be the right solution. [This post](https://stackoverflow.com/a/21491131/712526) (and others on that page) goes into more detail. – jpaugh Aug 28 '17 at 15:54
14

I think what you want is lazy.

let resource = 
    lazy(
        // expensive value init here
    )

Then later when you need to read the value...

resource.Value

If you never call the Value property, the code inside the lazy block never gets run, but if you do call it, that code will be run no more than once.

Joel Mueller
  • 27,176
  • 8
  • 62
  • 85
  • 1
    The drawback of this suggestion is that calling `resource.Value` will always return the same value because it is executed only once, whereas the return value of the "parameterless" function can vary on every call. – Oliver Feb 05 '16 at 22:28
  • 2
    Right, but if the function is pure in functional terms and has no arguments, it will always return the same value anyway. – Joel Mueller Feb 06 '16 at 16:06
  • I can think of two functions that always return a different value: a random number generator and a current date/time provider. I have no idea what _purity_ means in _functional terms_, but those seem to be valid examples where your lazy value approach would not work. – Oliver Feb 08 '16 at 18:37
  • 1
    A function is "pure" if, given the same input, it always produces the same output. I didn't claim lazy works for all approaches, or even that impure functions are bad in some way. Just clarifying for which class of functions lazy would be suitable. – Joel Mueller Feb 09 '16 at 00:57
  • I mean, the OP was about pure functions with no arguments after all, the exact thing that "lazy" is good at. – Joel Mueller Feb 09 '16 at 00:59
  • 1
    Now I see. Thank you for taking the time to explain these things - I've re-read the question and your solution is actually exactly what the OP was after :-) – Oliver Feb 09 '16 at 08:25