2

Given this F# library code

From Main

open FileMaint
let log_file = FileMaint.fm.generate_log_file

From library

module FileMaint
.
.
.
[<AutoOpen>]
(* return_unique_file_name just returns as close to a unique file name piece based on clock time. *)
module fm =


(* Checks for presense of a file name. *)
    let is_file_present file_name =
        let rc =
            if File.Exists file_name then
                true
            else
                false
        rc

    (* Initialize a log file. *)
    let generate_log_file =
        let log_file_name = 
            if File.Exists(base_log_file) then
                test_and_reset_unique_file_name local_dir base_log_file
            else
                base_log_file

        use fH = new StreamWriter(log_file_name, true)
        fH.WriteLine(generate_time_stamp + ": " + log_file_name + " initialized.")

From my "main", I can step into call is_file_present, but I cannot step into, nor will generate_log_file execute.

However, after defining parenthesis on the generate_log_file library function, like this generate_log_file (), and then calling it, the function executes (and I can step into the function).

So, it seems like empty parentheses indicate a placeholder when a function has no parameters. Is that correct?

octopusgrabbus
  • 10,555
  • 15
  • 68
  • 131
  • Might want to include your main code as well (or at least the relevant bits) –  May 24 '17 at 13:46
  • @Will I have added some additional context. – octopusgrabbus May 24 '17 at 13:52
  • `()` is just unit. In F# each function has to have one parameter, and each function has to return something. In case you don't pass any variables into the function you indicate that with (). And ditto for the return value, if it has side effects (e.g. print to the screen or write to a file) it will also return (). – s952163 May 24 '17 at 14:03
  • 1
    See my take on unit and tuples here. https://stackoverflow.com/a/20708114/2200185 – hocho May 24 '17 at 17:32

2 Answers2

6

Empty parentheses are a value of type unit. This value is just like any other value, not principally different from numbers or strings.

let a = 5
let b = "abc"
let c = ()

> val a: int = 5
> val b: string = "abc"
> val c: unit = ()

This type unit is special in that it only has one value, no others. But in other ways, it's just like any other type. You can use it for function parameters for example:

let f (u: unit) = ...

But because it only ever has one value, it is possible to use the value itself as parameter pattern:

let f () = ...

You can do that with other types as well:

let g 42 = ...
let h "abc" = ...

And this will compile, but it will give you a warning that there are other values of int besides 42, and you haven't defined what the function should do for them. But with unit there can be no other values besides (), so there is no warning. You can even make an equivalent type yourself:

type T = A
let f A = 42   // no warning here, because A is the only possible value of type T

Now, getting back to your example: in your original code, generate_log_file is not a function, but a value. It is calculated during initialization, and when you think you're "calling" it, you're just referencing that precalculated value. No calling is going on, because there is nothing to call.

And the reason that it's a value and not a function is that it doesn't have any parameters. Yep, it's a simple rule: if you have parameters, you're a function; if not, you're a value. So once you give it a parameter, it becomes a function, and that's why you can step in.

Fyodor Soikin
  • 78,590
  • 9
  • 125
  • 172
2

As it stands the value of generate_log_file is a value initialized when module fm is initialized, most likely during assembly startup. This is debuggable if you insert Debugger.Break strategically.

By changing the code to let generate_log_file () ... generate_log_file is now a function that you invoke using the unit value () making it easier to debug.

In F# you always invoke a function using a single value, () is the one and only value of type unit.

PS.

let generate_log_file () ... uses pattern matching of arguments to avoid the need to type let generate_log_file (_ : unit) .... The following is legal too albeit somewhat useless let generate_log_file 0 ....