So I've just about finished my first F# program, with my only functional background being a little bit of knowledge of Haskell (read: Haven't really produced any programs in it).
After experiencing some boggling behavior, I came to realize that F# makes a differentiation between:
prepareDeck = allSuits |> List.collect generateCards |> shuffle
and
prepareDeck() = allSuits |> List.collect generateCards |> shuffle
I noticed that it "caches" the former, never recalculating it if it's called again, whereas it treats the latter like a normal function. You can't tell the difference if the function in question doesn't have side effects, obviously, but my shuffle
did!
Was this supposed to be common knowledge? I haven't seen it mentioned on any tutorial materials yet. Is the reason just a weakness in the parser, kinda like how you have to declare a function before you use it?
Most F# material does explain that all top-level statements in a module are executed from top-down on declaration. In other words, what you've declared isn't a function, but a value which is bound once when the program runs.
It really helps to see the reflected code. I have a simple file:
let juliet = "awesome"
let juliet2() = "awesome"
The compiled code looks something like this:
public static string juliet
{
[CompilerGenerated, DebuggerNonUserCode]
get
{
return "awesome";
}
}
//...
public static string juliet2()
{
return "awesome";
}
So one is a static property, the other is a function. This is a desirable property, because imagine if we had something like this:
let x = someLongRunningDatabaseCall()
We only want x
to be bound once, we don't want it to invoke database function everytime we access x
.
Additionally, we can write interesting code like this:
> let isInNebraska =
printfn "Creating cities set"
let cities = set ["Omaha"; "Bellevue"; "Lincoln"; "Papillion"; "La Vista"; "Ralston"]
fun n -> cities.Contains(n);;
Creating cities set
val isInNebraska : (string -> bool)
> isInNebraska "Omaha";;
val it : bool = true
> isInNebraska "Okaloosa";;
val it : bool = false
Since isInNebraska
is a value, its evaluated immediately. It just so happens that its datatype is (string -> bool)
, so it looks like a function. As a result, we only fill our cities
set once even if we invoke the function 1000 times.
Let's compare that code to this:
> let isInNebraska2 n =
printfn "Creating cities set"
let cities = set ["Omaha"; "Bellevue"; "Lincoln"; "Papillion"; "La Vista"; "Ralston"]
cities.Contains(n);;
val isInNebraska2 : string -> bool
> isInNebraska2 "Omaha";;
Creating cities set
val it : bool = true
> isInNebraska2 "Okaloosa";;
Creating cities set
val it : bool = false
Oops, we're creating a new cities set everytime we invoke the function.
So there is definitely a legitimate and real distinction between values and functions.
This is how things work in practically every language with side effects.
let name = expr
runs the code 'now', and may cause side-effects if expr
has effects. Subsequent references to name
have no effects. Whereas
let name() = expr
defines a function, has no effects now, and will evaluate (and have effects) every time name()
is invoked.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With