Active pattern broken in F# 3.0

557 views Asked by At

This active pattern compiles with F# 2.0:

let (|Value|_|) value = // 'a -> 'T option
  match box value with
  | :? 'T as x -> Some x
  | _ -> None

but, in F# 3.0, emits the error:

Active pattern '|Value|_|' has a result type containing type variables that are not determined by the input. The common cause is a [sic] when a result case is not mentioned, e.g. 'let (|A|B|) (x:int) = A x'. This can be fixed with a type constraint, e.g. 'let (|A|B|) (x:int) : Choice = A x'

I tried:

let (|Value|_|) value : 'T option = ...

and:

let (|Value|_|) (value: 'U) = ...

How can it be fixed?

Environments: Visual Studio 2012 (RTM) and FSI v11.0.50727.1

EDIT: Here's a simpler repro:

let (|X|) x = unbox x
4

There are 4 answers

1
Brian On BEST ANSWER

There was a bug in the F# 2.0 compiler where the compiler did incorrect analysis and bad code generation for certain Active Patterns with free type variables in the result; a simple repro is

let (|Check|) (a : int) = a, None
//let (|Check|) (a : int) = a, (None : int option)

let check a = 
    match a with
    | Check (10, None) -> System.Console.WriteLine "10"
    | Check (20, None) -> System.Console.WriteLine "20"

check 10
check 20

which generates a weird warning at compile-time and compiles into seemingly incorrect code. I am guessing that our attempt to fix this bug (and restrict some crazy cases) in F# 3.0 also broke some legal code as collateral damage of the fix.

I'll file another bug, but for F# 3.0, it sounds like you'll need to use one of the workarounds mentioned in other answers.

2
Tomas Petricek On

I did not install the new version yet, but I agree this looks a bit fishy. I guess there may be a good reason for this restriction, but your example in the other question seems quite compeling.

As a workaround, I think that adding a witness parameter (that is not used, but hints what the type of the result is going to be) could work:

let (|Value|_|) (witness:unit -> 'T) value : 'T option =
  match box value with 
  | :? 'T as x -> Some x 
  | _ -> None 

Of course, this makes the use a bit uglier, because you need to come up with some argument. In the above, I used witness of type unit -> 'T, hoping that the following might compile:

let witness () : 'T = failwith "!"

match box 1 with 
| Value witness 1 -> printfn "one"

If that does not work, then you can probably try using witness parameter of type 'T (but then you have to provide an actual function, rather than just a generic function).

0
kvb On

See my answer to your other question for some thoughts on how to work around the issue and one reason that such active patterns might be undesirable. I'm not sure whether the breaking change was intended.

0
desco On

for the sake of completeness, one more workaround:

type Box<'R> = Box of obj

let (|Value|_|) ((Box x) : Box<'R> ) : 'R option =
  match x with 
  | :? 'R as x -> Some x 
  | _ -> None 

let check t =
    match Box t with
    | Value 1 -> printfn "one"
    | Value 2 -> printfn "two"

check 1 // one
check 2 // two

however it still will suffer from the problem mentioned by @kvb in another thread. Personally I'll prefer @kvb's version with parameterized active pattern.