Подтвердить что ты не робот

Перегрузка оператора F # для преобразования нескольких различных единиц измерения

Я хочу иметь возможность сделать это:

let duration = 1<hours> + 2<minutes> + 3<seconds>

со следующими типами и функциями (и, возможно, с большим количеством единиц измерения):

type [<Measure>] seconds     
type [<Measure>] minutes
type [<Measure>] hours

let seconds_per_minute = 60<seconds> / 1<minutes>
let minutes_per_hour = 60<minutes> / 1<hours>

let minutes_to_seconds minutes seconds = minutes * seconds_per_minute + seconds
let hours_to_minutes hours minutes = hours * minutes_per_hour + minutes

Итак, в основном "hours_to_minutes" следует использовать для добавления часов и минут, а "minutes_to_seconds" следует использовать для добавления минут и секунд, когда я набираю его, как указано выше.

Можно ли это сделать в F #?

4b9b3361

Ответ 1

На самом деле это возможно, есть способ сделать это:

type [<Measure>] seconds     
type [<Measure>] minutes
type [<Measure>] hours

let seconds_per_minute = 60<seconds> / 1<minutes>
let minutes_per_hour = 60<minutes> / 1<hours>

let minutes_to_seconds minutes seconds = minutes * seconds_per_minute + seconds
let hours_to_minutes hours minutes = hours * minutes_per_hour + minutes

type D1 = D1
type D2 = D2

type Sum = Sum with
  static member inline ($) (Sum, _:^t when ^t: null and ^t: struct) = id
  static member inline ($) (Sum, b)              = fun _  _  a -> a + b
  static member        ($) (Sum, b:int<minutes>) = fun D1 _  a -> hours_to_minutes   a b
  static member        ($) (Sum, b:int<seconds>) = fun D1 D2 a -> minutes_to_seconds a b    

let inline (+) a b :'t = (Sum $ b) D1 D2 a

let duration = 1<hours> + 2<minutes> + 3<seconds>

Но он действительно взломан, я бы не рекомендовал его.

UPDATE

На основе комментариев здесь приведены некоторые ответы:

  • В этом методе используются перегрузки, которые разрешаются во время компиляции, поэтому во время выполнения нет штрафа за производительность. Он основан на том, что я написал некоторое время назад в моем блоге.

  • Чтобы добавить больше перегрузок, вам нужно будет добавить дополнительные фиктивные параметры (D3, D4,...), и в конечном итоге, если вы решите добавить некоторые перегрузки, которые конфликтуют с существующим, вам может потребоваться используйте тернарный оператор (?<-) или вызов функции с явными ограничениями статического члена. Вот пример кода.

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

  • Техника Фила Трелфорда (упомянутая в ответе Рида) работает во время выполнения, третий вариант - использовать типы phantom, это может потребовать меньше хаков.

Заключение

Если бы мне пришлось выбирать между всеми альтернативами, я бы использовал этот метод, но был более явным на сайте вызова, я имею в виду, что я бы определял функции преобразования, такие как minutes, seconds, и таким образом на сайте вызова я писал бы

let duration = seconds 1<hours> + seconds 2<minutes> + 3<seconds>

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

Ответ 2

Это невозможно непосредственно внутри F #. Невозможно, чтобы "автоматическое преобразование" выполнялось напрямую, без указания преобразования для типов. Вам нужно будет явно вызвать ваши функции преобразования (seconds_per_minute и т.д.).

Однако, Фил Трелфорд продемонстрировал механизм, с помощью которого вы могли бы создавать классы времени выполнения, которые поддерживают это, хотя и с немного отличающимся синтаксисом. Используя его типы, вы можете написать:

let duration = 1.0 * SI.hours + 2.0 * SI.minutes + 3.0 * SI.seconds