Inconsistent pattern matching behavior

type Bar = A | B type Foo = C of Bar | D of Bar let case = Unchecked.defaultof<Foo>;; match case with | CA -> "" | CB -> "" | _ -> "Matches";; match case with | CA -> "" | DB -> "" | _ -> "Throws" 

A quick switch to the F # Language Spec, nothing about null-test (which I can't do anyway) seemed to be related, and both types seem to be reference types (AFAIK).

I would suggest that the behavior in the first case is correct.

+4
source share
5 answers

Comparing a template with DU with two cases compiles in if/else with the type of tests. If you translate your examples, the behavior is obvious.

 match case with | CA -> "" | CB -> "" | _ -> "Matches" 

translates to

 if (case is C) ... else "Matches" 

and

 match case with | CA -> "" | DB -> "" | _ -> "Throws" 

translates to

 if (case is D) ... else //must be C //check tag BANG! NRE 

And kvb example: match case with | C _ -> "C" | D _ -> "D" match case with | C _ -> "C" | D _ -> "D"

 if (case is D) //... else //must be C "C" 

I suppose you could think of it as a reasonable optimization (vs. if (case is D) {...} else if (case is C) {...} else { MatchFailureException } ), given that the behavior is null undefined.

Add the third case to Foo and the problem goes away.

+2
source

I think all bets are disabled if you use Unchecked.defaultof<_> (thus, "Unchecked" ;-)). Null is not considered a valid value for the type Foo from the point of view of F # (although this is from the point of view of .NET), so I do not think that the semantics of pattern matching are defined.

What are you trying to do?

+3
source

So, after reading the spec, the first hint here

b) Types with a null value as an abnormal value. These are types that do not allow a null literal, but have a null value as an abnormal value.

Types in this category:

o All types of F # list, record, tuple, function, class and interface.

o All union types F #, except those whose null value is equal to the normal value (as discussed in the next section).

For these types, the use of a null literal is not directly permitted. However, strictly speaking, you can generate null for these types using certain functions, such as Unchecked.defaultof. For these types, null is considered an abnormal value. The behavior of operations with respect to zero values ​​is defined in Β§ 6.9.

This seems to suggest that when passing in a null value for your union, there might be some undefined behavior, since the value is "abnormal", section 6.9 is not particularly useful

Looking at the definition for _ , it seems that you are right that this is a mistake - she states

7.1.7 Substitution Patterns

Sample _ is a wildcard and matches any input. For example:

allow categorization x =

 match x with | 1 -> 0 | 0 -> 1 | _ -> 0 

I think the most relevant hints, but later, where compiled methods for DU are listed

8.5.3 Compiled union type form for use with other CLI languages

The compiled union type U will have:

Β· One CLI property for a static UC getter for each null union of case C. This will get a singleton object representing this case.

. One CLI-nested UC type for each case with zero union C. This type will have instance properties Item1, Item2 .... for each field of the union case, or one instance property object if it is only one field. Compiled type joins with one case do not have a nested type. Instead, the type of union itself plays the role of the type of corps.

Β· One static UI.NewC CLI method for each case that is not associated with a null union C. This will build an object for this case.

. One u.IsC CLI instance property for each C case, which returns true or false for the case.

. One property of the u.Tag CLI instance for each C case that selects or evaluates an integer tag that matches the case.

From this, you can see that all validation methods are instance methods that require nonemptiness. The sine of null is "abnormal", the generated code does not check, so it throws.

I think you could argue that this is an infact error based on the definition of _ . However, to fix it, you will need to insert null checks before each DU pattern matching check, which will significantly reduce the code, so I doubt it will be fixed.

+3
source

The description of the Unchecked.defaultof method ends with the sentence "This function is unsafe in the sense that some F # values ​​do not have valid zero values," which is the case here.

Try the following:

 let isNull = (case = null) ;; let isNull = (case = null) ;; ---------------------^^^^ stdin(3,22): error FS0043: The type 'Foo' does not have 'null' as a proper value > 

DUs are designed to be immutable and do not have valid zero values.

+2
source

I cannot confirm this at the moment, but will the AddNullLiteral attribute for the types match the match?

0
source

Source: https://habr.com/ru/post/1384401/


All Articles