Активный шаблон сломан в F# 3.0

Этот активный шаблон компилируется с F# 2.0:

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

но в F# 3.0 выдает ошибку:

Активный паттерн '| Значение |_|' имеет тип результата, содержащий переменные типа, которые не определены входными данными. Распространенной причиной является [sic], когда случай результата не упоминается, например, 'let (|A|B|) (x:int) = A x'. Это можно исправить с помощью ограничения типа, например 'let (|A|B|) (x:int): Choice = A x'

Я старался:

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

а также:

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

Как это можно исправить?

Среды: Visual Studio 2012 (RTM) и FSI v11.0.50727.1

РЕДАКТИРОВАТЬ: Вот более простое воспроизведение:

let (|X|) x = unbox x

4 ответа

Решение

В компиляторе F# 2.0 была ошибка, из-за которой компилятор неправильно анализировал и генерировал некорректный код для некоторых активных шаблонов с переменными свободного типа в результате; простое воспроизведение

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

который генерирует странное предупреждение во время компиляции и компилирует в на первый взгляд некорректный код. Я предполагаю, что наша попытка исправить эту ошибку (и ограничить некоторые сумасшедшие случаи) в F# 3.0 также нарушила некоторый правовой кодекс как побочный ущерб от исправления.

Я напишу еще одну ошибку, но для F# 3.0 кажется, что вам нужно использовать один из обходных путей, упомянутых в других ответах.

Я еще не установил новую версию, но я согласен, что это выглядит немного подозрительно. Я думаю, что для этого ограничения могут быть веские причины, но ваш пример в другом вопросе кажется довольно убедительным.

В качестве обходного пути я думаю, что добавление параметра-свидетеля (который не используется, но намекает, каким будет тип результата) может работать:

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

Конечно, это делает использование немного уродливым, потому что вам нужно придумать какой-то аргумент. В приведенном выше, я использовал свидетель типа unit -> 'T, надеясь, что следующее может скомпилировать:

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

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

Если это не сработает, то вы можете попробовать использовать параметр-свидетель типа 'T (но тогда вы должны предоставить реальную функцию, а не просто обобщенную функцию).

Ради полноты еще один обходной путь:

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

однако он все еще будет страдать от проблемы, упомянутой @kvb в другом потоке. Лично я предпочту версию @ kvb с параметризованным активным шаблоном.

Посмотрите мой ответ на ваш другой вопрос, чтобы узнать, как обойти эту проблему, и одну из причин, по которой такие активные шаблоны могут быть нежелательны. Я не уверен, было ли решающее изменение предназначено.

Другие вопросы по тегам