В F # возможно ли иметь функцию tryParse, которая определяет целевой тип

В настоящее время мы делаем это...

let parseDate defaultVal text = 
match DateTime.TryParse s with
| true, d -> d
| _       -> defaultVal

Можно ли сделать это...

let d : DateTime = tryParse DateTime.MinValue "2015.05.01"

person George    schedule 16.10.2015    source источник
comment
См. также stackoverflow.com/q/4656864/82959.   -  person kvb    schedule 16.10.2015


Ответы (1)


да. Добро пожаловать в мир ограничений элементов, значений ref и byref.

  let inline tryParseWithDefault 
      defaultVal 
      text 
      : ^a when ^a : (static member TryParse : string * ^a byref -> bool) 
      = 
    let r = ref defaultVal
    if (^a : (static member TryParse: string * ^a byref -> bool) (text, &r.contents)) 
    then !r 
    else defaultVal
  1. defaultVal и text являются формальными параметрами и будут выведены. Здесь text уже ограничено значением string, поскольку оно используется в качестве первого параметра в вызове статического метода SomeType.TryParse, как будет объяснено позже. defaultVal ограничено тем, чем является ^a, поскольку это возможное значение результата для выражения if..then..else.
  2. ^a – это статически разрешенный параметр типа (по сравнению с параметром универсального типа в форме 'a). В частности, ^a будет преобразован во время компиляции в определенный тип. Следовательно, функция, в которой она размещена, должна быть помечена inline, что означает, что каждый вызов функции станет заменой на месте при этом вызове этим телом функции, при этом каждый параметр статического типа станет определенным типом; в этом случае, каким бы ни был тип defaultVal. Не существует ограничений базового типа или типа интерфейса, ограничивающих возможный тип defaultVal. Однако вы можете указать статические и экземплярные ограничения члена, как это сделано здесь. В частности, значение результата (и, следовательно, тип defaultVal), по-видимому, должен иметь статический член с именем TryParse, который принимает string и ссылку на изменяемый экземпляр этого типа и возвращает значение boolean. Это ограничение становится явным благодаря заявленному типу возвращаемого значения в строке, начинающейся с : ^a when .... Тот факт, что defaultVal сам по себе является возможным результатом, ограничивает его тип того же типа, что и ^a. (Ограничение также подразумевается в другом месте функции, что необязательно, как объяснено ниже).
  3. : ^a when ^a : (static .... описывает тип результата ^a как наличие статического члена с именем TryParse типа string * ^a byref -> bool. Другими словами, тип результата должен иметь статический член с именем TryParse, который принимает string, ссылку на свой экземпляр (и, следовательно, изменяемый экземпляр), и возвращает значение boolean. Это описание показывает, как F# соответствует определению TryParse в .Net для типов DateTime, Int32, TimeSpan и т. д. Обратите внимание, что byref является эквивалентом F# модификатора параметра out или ref в C#.
  4. let r = ref defaultVal создает ссылочный тип и копирует в него предоставленное значение defaultVal. ref — это один из способов создания изменяемых типов в F#. Другой с ключевым словом mutable. Разница в том, что mutable хранит свое значение в стеке, а ref хранит его в основной памяти/куче и содержит адрес (в стеке) для него. Последняя версия F# будет стремиться автоматически обновить изменяемые обозначения до ref в зависимости от контекста, что позволит вам кодировать только с точки зрения изменяемых.
  5. if (^a : (static... — это инструкция if по результатам вызова метода TryParse для статически выводимого типа ^a. Этот TryParse передается, (text, &r.contents), по его подписи (string * ^a byref). Здесь &r.contents предоставляет ссылку на изменяемое содержимое r (имитирующее параметр C# out или ref) в соответствии с ожиданиями TryParse. Обратите внимание, что здесь мы не оговариваемся, и некоторые тонкости F# для взаимодействия с платформой .Net не распространяются так далеко; в частности, автоматическое свертывание разделенных пробелами параметров F# в параметры функции .net framework в виде кортежа недоступно. Следовательно, параметры предоставляются функции в виде кортежа (text, &r.contents).
  6. !r - это то, как вы читаете эталонное значение. r.Value тоже подойдет.

Методы TryParse, предоставляемые .Net, похоже, всегда устанавливают значение для параметра out. Следовательно, значение по умолчанию строго не требуется. Однако вам нужен держатель значения результата, r, и он должен иметь начальное значение, даже нулевое. Мне не понравился ноль. Другой вариант, конечно, состоит в том, чтобы наложить другое ограничение на ^a, которое требует некоторого свойства значения по умолчанию.

Следующее последующее решение устраняет необходимость в параметре по умолчанию, используя Unchecked.defaultof< ^a > для получения подходящего значения заполнителя из предполагаемого типа результата (да, это похоже на волшебство). Он также использует тип Option для характеристики успешного и неудачного получения значения результата. Таким образом, тип результата ^a option.

tryParse 
    text 
    : ^a option when ^a : (static member TryParse : string * ^a byref -> bool) 
    = 
  let r = ref Unchecked.defaultof< ^a >
  if (^a : (static member TryParse: string * ^a byref -> bool) (text, &r.contents)) 
  then Some (!r)
  else None

И, согласно предложениям @kvb, возможна следующая краткость. В этом случае вывод типа используется как для определения ограничения типа для ^a в результате его вызова в выражении if (^a : ...)), так и для установления типа изменяемого буфера r для выходного параметра TryParse. С тех пор я узнал, как FsControl делает некоторые это волшебство

let inline tryParse text : ^a option = 
  let mutable r = Unchecked.defaultof<_>
  if (^a : (static member TryParse: string * ^a byref -> bool) (text, &r)) 
  then Some r
  else None

let inline tryParseWithDefault defaultVal text : ^a = 
  match tryParse text with
  | Some d -> d
  | _      -> defaultVal

При этом использование будет...

> let x:DateTime option = tryParse "December 31, 2014";;
val x : DateTime option = Some 2014-12-31 12:00:00 a.m.

> let x:bool option = tryParse "false";;
val x : bool option = Some false

> let x:decimal option = tryParse "84.32";;    
val x : decimal option = Some 84.32M

В случае использования ограничений типа для члена экземпляра, таких как тип, ограничивающий пользовательский оператор поиска динамического члена fsharp, ?, так что тип цели должен содержать член FindName:string->obj для использования в разрешении запросов, синтаксис выглядит следующим образом:

let inline (?) (targetObj:^a) (property:string) : 'b =
    (^a : (member FindName:string -> obj) (targetObj, property)) :?> 'b

Примечание:

  1. Подпись методов экземпляра должна явно указывать их объект self, который обычно является скрытым первым параметром.
  2. Это решение также продвигает любой результат типа 'b

Пример использования будет следующим:

let button : Button = window?myButton
let report : ReportViewer = window?reportViewer1
person George    schedule 16.10.2015
comment
В F#+ эта функция определена аналогичным образом, а также частичная версия parse github.com/gmpl/FSharpPlus/blob/ - person Gus; 16.10.2015
comment
В качестве второстепенного комментария по стилю использование let mutable x = Unchecked.defaultof<_>, а затем использование &x в качестве аргумента для вызова метода кажется мне более чистым, чем введение фактического значения ref; кроме того, сигнатура может быть выведена из определения (поэтому вам не нужно записывать ограничение дважды), хотя, возможно, вы включили его по педагогическим соображениям. - person kvb; 16.10.2015
comment
@Gustavo Я не знал о проекте FSharpPlus и лишь мимолетно знал о FsControl. Спасибо, что открыл мне глаза. Они определили TryParse похожим, но более элегантным способом :) >github.com/gmpl/FsControl/blob/ - person George; 16.10.2015
comment
@kvb Спасибо за понимание. Использование ограничений членов для вызова методов (статических и экземпляров) не сразу было очевидно для меня из материалов MSDN. Я был удивлен, когда обнаружил его. Я добавил пример, включающий ваши «стилистические» предложения. - person George; 16.10.2015