F# - Function with no arguments?
Asked Answered
V

2

46

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?

Vanscoy answered 9/5, 2010 at 12:55 Comment(4)
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.Weatherglass
@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.Weatherglass
In Haskell pure functions are actually pure. The only reason in F# that "reality gets in the way" is that F# functions are not guaranteed to be pure. It doesn't matter whether anything is lazy or not. In Haskell, a no-arg function can do nothing except return a value. @WeatherglassDisrobe
I should add that all functions in Haskell are pure. My previous comment makes it sound like there are pure and not pure functions. Everything is pure.Disrobe
H
69

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 ().)

Havelock answered 9/5, 2010 at 13:0 Comment(5)
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?Nevers
thanks for response, but it doesn't compile. Is it possible to write a record type like this?Nevers
@Nevers It compiles fine for me with unit.Havelock
Joel Mueller's answer 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().Hitormiss
@Oliver, actually, using lazy() will have different memory/performance characteristics, and may not always be the right solution. This post (and others on that page) goes into more detail.Weatherglass
M
16

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.

Metz answered 9/5, 2010 at 18:0 Comment(7)
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.Hitormiss
Right, but if the function is pure in functional terms and has no arguments, it will always return the same value anyway.Metz
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.Hitormiss
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.Metz
I mean, the OP was about pure functions with no arguments after all, the exact thing that "lazy" is good at.Metz
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 :-)Hitormiss
Random number generator functions exist in pure languages as well. Remember that random numbers (that don't use io) are pseudorandom, each iteration of the random number takes a seed to create the new value and new seed, saves the seed and returns the random value. In a pure language you get the seed and value back, and pass the seed into the next call to the generator. Re-seeding the generator is an io operation. Haskell uses monads to accomplish this so you don't actually have to pass the seed around like that, but that is what's actually happening under the syntax sugar.Disrobe

© 2022 - 2024 — McMap. All rights reserved.