0

Alright, so I'm a happy fsx-script programmer, because I love how I can have the compiler shout at me when I do mistakes before they show up at runtime.

However I've found a case which really bothers me because I was expecting that by doing some refactoring (i.e.: adding an argument to a function) I was going to be warned by the compiler about all the places where I need to put the new argument. But, not only this did not happen, fsharpi ran my script and ignored the function call completely!! :(

How can I expect to refactor my scripts if this happens?

Here is my code:

let Foo (bar: string) =
    Console.WriteLine("I received " + bar)

Foo("hey")

It works.

Now, later, I decide to add a second argument to the function (but I forget to add the argument to all the calls to it):

let Foo (bar: string) (baz: bool) =
    Console.WriteLine("I received " + bar)

Foo("hey")

The result of this is: instead of the compiler telling me that I'm missing an argument, it is fsharpi running the script and ignoring the call to Foo! Why?

PS: I know the difference between currying and tuples, so I know Foo("hey") becomes a function (instead of a function call), because of partial application. But I want to understand better why the compiler is not expecting a function evaluation here, instead of seeing a function and ignoring it. Can I enable a warningAsError somehow? I would like to avoid resorting to using tuples in order to workaround this problem.

user1623521
  • 340
  • 1
  • 16
ympostor
  • 909
  • 7
  • 16
  • 3
    Bound to be a duplicate, but you have discovered currying - `Foo("hey")` is a function – John Palmer Jul 05 '16 at 11:48
  • 1
    The answer here answers your question: http://stackoverflow.com/questions/2725202/f-function-calling-syntax-confusion?rq=1. Also, you were almost certainly warned about some weird return types in various places by the compiler – John Palmer Jul 05 '16 at 11:49
  • 3
    I know I'm using currying, but I thought it was the preferred way of using arguments in F# (as opposed to C#-style arguments). And then I know that Foo("hey") is a function, but shouldn't the compiler still throw an error? – ympostor Jul 05 '16 at 11:51
  • If you were actually using `Foo` you almost certainly got an error/warning - For example `if true then Foo "hey" else ()` won't compile – John Palmer Jul 05 '16 at 11:52
  • I've read the answer linked and it's just explaining currying, which I already knew about. What doesn't explain is why the fsharpi compiler doesn't throw an error when finding a function. Shouldn't it expect to find all arguments to the function if it's not a function declaration but a call to it? – ympostor Jul 05 '16 at 11:56
  • In your first example, Foo(hey) has sufficient parameters to be fully evaluated, so it executes the writeline and returns unit (like no return type). In the second example, Foo(hey) evaluates to a function that takes a bool only – marklam Jul 05 '16 at 11:57
  • but I'm writing a script, if I write Foo() I simply want Foo to be called, I don't need to write an if-else structure for that – ympostor Jul 05 '16 at 11:57
  • why should it - it is perfectly fine to write a function which returned `Foo "hey"` - in fact that is the whole point of using currying – John Palmer Jul 05 '16 at 11:58
  • I said I completely understand that `Foo "hey"` is a function, yes, but inside an F# script, all lines should be expected to be evaluated as `unit`, except when declaring a function/module/type – ympostor Jul 05 '16 at 12:00
  • 2
    In a .fs file, an expression which doesn't evaluate to unit is only a warning. (IIRC) – marklam Jul 05 '16 at 12:01
  • @marklam, that's good to know, is there a way to make `fsharpi` report warnings as errors? – ympostor Jul 05 '16 at 12:04
  • Can't see anything, sorry. – marklam Jul 05 '16 at 12:05
  • 2
    Question what do you think `1;;` in fsi should do? How is it different to `foo "hey"`? – John Palmer Jul 05 '16 at 12:05
  • I think fsi should report an error when seeing `1;;`, definitely. @marklam, there's a --warnaserror flag for fsharpi (http://manpages.ubuntu.com/manpages/wily/man1/fsharpi.1.html) but even when using it, it's not reporting the warning you mention – ympostor Jul 05 '16 at 12:07
  • 2
    Seriously? the whole point of F# interactive is that it is interactive - if you have a value it prints out what its value is – John Palmer Jul 05 '16 at 12:09
  • 1
    I use F# for scripting, not for REPL, do you have a better suggestion for me? – ympostor Jul 05 '16 at 12:12
  • what you want to achieve? – FoggyFinder Jul 05 '16 at 12:21
  • statically typed scripts that don't break when refactored – ympostor Jul 05 '16 at 12:26
  • I don't know of any tools for such refactoring F# code. Your question is a little misleading. – FoggyFinder Jul 05 '16 at 12:27

1 Answers1

2

The fsharpi (or fsi if you're on Windows) interpreter makes no distinction between running a script and typing code at the interactive prompt (or, most often, submitting code from your editor via a select-and-hit-Alt-Enter keyboard shortcut).

Therefore, if you got what you're asking for -- fsharpi issuing a warning whenever a script line has a return value that isn't () -- it would ruin the value of fsharpi for the most common use case, which is people using an interactive fsharpi session to test their code, and rapidly iterate through non-working prototypes to get to one that works correctly. This is one of F#'s great strengths, and giving you what you're asking for would eliminate that strength. It is therefore never going to happen.

BUT... that doesn't mean that you're sunk. If you have functions that return unit, and you want fsharpi to give you a compile-time error when you refactor them to take more arguments, you can do it this way. Replace all occurrences of:

Foo("hey")

with:

() = Foo("hey")

As long as the function Foo has only one argument (and returns null), this will evaluate to true; the true value will be happily ignored by fsharpi, and your script will run. However, if you then change Foo to take two arguments, so that Foo("hey") now returns a function, the () = Foo("hey") line will no longer compile, and you'll get an error like:

error FS0001: This expression was expected to have type
    unit
but here has type
    'a -> unit

So if you want fsharpi to refuse to compile your script when you refactor a function, go through and change your calls to () = myfunc arg1 arg2. For functions that don't return unit, make the value you're testing against a value of that function's return type. For example, given this function:

let f x = x * 2

You could do

0 = f 5

This will be false, of course, but it will compile. But if you refactor f:

let f x y = x * 2 + y

Now the line 0 = f 5 will not compile, but will give you the error message:

error FS0001: This expression was expected to have type
    int
but here has type
    int -> int

To summarize: you won't ever get the feature you're looking for, because it would harm the language. But with a bit of work, you can do something that fits your needs.

Or in other words, as the famous philosopher Mick Jagger once put it:

You can't always get what you want. But if you try, sometimes you might find you get what you need.

rmunn
  • 34,942
  • 10
  • 74
  • 105
  • thanks so much for providing a helpful answer instead of the conservative and dismissive style comments I was getting in the question; I will try this and report back (and mark as accepted accordingly); btw, do you think contributing a "flag" to `fsi` to make it work in a non-interactive way, that is, reporting an error for expressions that resolve to a value instead of `unit`, would be accepted? – ympostor Jul 06 '16 at 04:10