Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inconsistent behavior in pattern matching

Tags:

f#

type Bar = A | B
type Foo = C of Bar | D of Bar
let case = Unchecked.defaultof<Foo>;;

match case with
| C A -> ""
| C B -> ""
| _ -> "Matches";;

match case with
| C A -> ""
| D B -> ""
| _ -> "Throws"

Quickly skimming over the F# Language Spec, nothing about null-test (which I can't do anyway) seemed related and both types seems to be reference type (AFAIK).

I would assume the behavior in the first case to be correct one.

like image 556
David Grenier Avatar asked Dec 28 '22 10:12

David Grenier


2 Answers

I think all bets are off once you use Unchecked.defaultof<_> (thus the "Unchecked" ;-)). Null isn't considered a valid value for type Foo from an F# perspective (although it is from the .NET perspective), so I don't think that the pattern matching semantics are defined.

What is it that you're trying to do?

like image 152
kvb Avatar answered Jan 05 '23 04:01

kvb


So reading the spec, the first hint is here

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

Types in this category are:

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

o All F# union types apart from those with null as a normal value (as discussed in the next paragraph).

For these types, the use of the null literal is not directly permitted. However it is, strictly speaking, possible to generate a null value 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 null values is defined in §6.9.

This does seem to suggest that on passing in a null value for your union could be some sort of undefined behaviour as the value is "abnormal", Section 6.9 isn't particularly helpful

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

7.1.7 Wildcard Patterns

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

let categorize x =

match x with

| 1 -> 0

| 0 -> 1

| _ -> 0

I think the most relevnat hints though are later on where the compiled methods for DU's are listed

8.5.3 Compiled Form of Union Types for Use from Other CLI Languages

A compiled union type U will have:

· One CLI static getter property U.C for each nullary union case C. This will get a singleton object representing that case.

· One CLI nested type U.C for each non-nullary union case C. This type will have instance properties Item1, Item2.... for each field of the union case, or a single instance property Item if there is only one field. A compiled union type with only one case does not have a nested type. Instead, the union type itself plays the role of the case type.

· One CLI static method U.NewC for each non-nullary union case C. This will construct an object for that case.

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

· One CLI instance property u.Tag for each case C that fetches or computes an integer tag corresponding to the case.

From this, you can see that all of the methods for checking are instance methods, which would require non-nullness. Sine null is "abnormal", the generated code doesn't bother checking, so it throws.

I think you could argue that this is infact a bug, based on the definition of _. However, fixing it would require inserting null checks before every DU pattern matching check, which would slow the code down significantly, so I doubt whether this will be fixed

like image 39
John Palmer Avatar answered Jan 05 '23 04:01

John Palmer