Образец, сочетающий тип теста и литерал

Активный шаблон в этом вопросе не удается скомпилировать после обновления до VS 2012 RTM. Он предоставляет способ выполнить проверку типа и сопоставить литерал в одном шаблоне. Например:

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

let getValue (name: string) (r: IDataReader) =
  match r.[name] with
  | null | :? DBNull | Value "" -> Unchecked.defaultof<_>
  | v -> unbox v

Можно ли это сделать без активного паттерна? Я понимаю when охранник может быть использован (:? string as s when s = ""), но это не может быть объединено с другими образцами.

2 ответа

Решение

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

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

Тем не менее, есть небольшая разница в производительности. Исходный активный шаблон переводится как:

public static FSharpOption<T> |Value|_|<a, T>(a value)
{
    object obj = value;
    if (!LanguagePrimitives.IntrinsicFunctions.TypeTestGeneric<T>(obj))
    {
        return null;
    }
    return FSharpOption<T>.Some((T)((object)obj));
}

то есть он выполняет тестирование типа и приведение типов. Это использование (match x with Value "" -> ...) переводится как:

FSharpOption<string> fSharpOption = MyModule.|Value|_|<object, string>(obj);
if (fSharpOption != null && string.Equals(fSharpOption.Value, ""))
{
    ...
}

В частности, типизированное значение, возвращаемое из шаблона, сопоставляется с использованием типичных преобразований компилятора для шаблонов (string.Equals для строк).

Обновленный шаблон переводится как:

public static FSharpOption<Unit> |Value|_|<T, a>(T x, a value)
{
    object obj = value;
    if (LanguagePrimitives.IntrinsicFunctions.TypeTestGeneric<T>(obj))
    {
        T y = (T)((object)obj);
        T y3 = y;
        if (LanguagePrimitives.HashCompare.GenericEqualityIntrinsic<T>(x, y3))
        {
            T y2 = (T)((object)obj);
            return FSharpOption<Unit>.Some(null);
        }
    }
    return null;
}

который использует общее равенство и менее эффективен, чем сопоставление с литералом. Использование немного проще, так как равенство запекается в шаблоне:

FSharpOption<Unit> fSharpOption = MyModule.|Value|_|<string, object>("", obj);
if (fSharpOption != null)
{
    ...
}

Во всяком случае, это работает. Но оригинал мне больше нравится.

Вы должны быть в состоянии использовать параметризованный активный шаблон:

let (|Value|_|) v x = 
    if unbox x = v then 
        Some() 
    else None

Использование должно выглядеть точно так же, как у вас сейчас.

редактировать

Хотя я не знаю, было ли преднамеренное изменение преднамеренным, я считаю, что обычно следует избегать активных шаблонов с общими типами возврата, не связанными с типами ввода. В сочетании с выводом типа они могут легко маскировать скрытые ошибки. Рассмотрим следующий пример, используя ваш оригинальный (|Value|_|) шаблон:

match [1] with
| Value [_] -> "Singleton"
| _ -> "Huh?"

Кажется, что это не то, что вы бы на самом деле пытались - название подразумевает, что Value должен использоваться только с литералами; параметризованные активные шаблоны включают именно этот сценарий.

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