Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Active pattern broken in F# 3.0

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
like image 867
Daniel Avatar asked Aug 15 '12 22:08

Daniel


3 Answers

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.

like image 197
Brian Avatar answered Nov 09 '22 22:11

Brian


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).

like image 3
Tomas Petricek Avatar answered Nov 09 '22 21:11

Tomas Petricek


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.

like image 2
desco Avatar answered Nov 09 '22 23:11

desco