简体   繁体   中英

F# can't understand the delimitation my multiple argument values have. Why?

I'm actually facing a potentially strange problem with the F# compiler. The following code gives me an error and I can't understand why:

    yield Animal(
        AnimalType(animal.AnimalType.Value),
error-->if Option.isSome animal.AnimalSubType then Some(AnimalSubType(animal.AnimalSubType.Value)) else None,
        AnimalSpecificSubType(animal.AnimalSpecificSubType.Value)
        )

The compiler complains that Animal should take 3 arguments but is in fact only taking 2. I guess it is failing to realize that the if argument ends at the end of the line (in the comma). I can get the code running by adding ()'s around the offending line:

    yield Animal(
        AnimalType(animal.AnimalType.Value),
--> OK  (if Option.isSome animal.AnimalSubType then Some(AnimalSubType(animal.AnimalSubType.Value)) else None),
        AnimalSpecificSubType(animal.AnimalSpecificSubType.Value)
        )

but I'd like to understand the issue. Why does this fix the issue and, for instance, this does not?

    yield Animal(
        AnimalType(amimal.AnimalType.Value),
error-->if (Option.isSome animal.AnimalSubType) then (Some(AnimalSubType(animal.AnimalSubType.Value))) else (None),
        AnimalSpecificSubType(animal.AnimalSpecificSubType.Value)
        )

I've not looked up over F#'s grammar, but I'd guess every time F# sees an if clause it will expect an else clause, potentially followed by an else clause. I'd guess that as long as I was careful with the else clause everything would be fine.

What's the matter here?

The compiler interprets the comma at the end of the error line as part of a tuple returned by the else branch, ie

yield Animal(
    a,
    if c then Some(b) else None,
    d 
    )

is parsed as:

yield Animal(
    a,
    if c then Some(b) else ((None, ...? )
    d 
    )

That's why your first fix works and not the second, as adding brackets in the second fix just wraps the first element of the phantom tuple, not the tuple itself, ie:

yield Animal(
    a,
    if c then Some(b) else (None),
    d 
    )

is parsed as:

yield Animal(
    a,
    if c then Some(b) else ((None), ...? )
    d 
    )

The problem has to do with how tuples are created in F#:

> let test (a,b,c) = "Test"
> test(1, if true then Some 1 else None,1);;

  test(1, if true then Some 1 else None,1);;
  -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error FS0001: Type mismatch. Expecting a
    'a * 'b * 'c    
but given a
    'a * 'b    
The tuples have differing lengths of 3 and 2

This makes it clear that the compiler thinks test is taking two parameters, but what does the compiler think these two parameters are? Since functions of this type are really just functions which take a single parameter as a tuple, we can deconstruct that line of code a bit like so:

> 1, if true then Some 1 else None,1;;

  1, if true then Some 1 else None,1;;
  ----------------------------^^^^^^

stdin(19,29): error FS0001: This expression was expected to have type
    int option    
but here has type
    'a * 'b

So you can see that the compiler is treating None,1 as the expression in the else of the if expression which is why the compiler is expecting an int option . To prevent this, you need to indicate to the compiler where the start and end of the if expression are. The easiest way of achieving this is by simply putting parenthesis around it like this:

> 1,(if true then Some 1 else None),1;;
val it : int * int option * int = (1, Some 1, 1)

Now we get the expected tuple.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM