2014-02-18 5 views
12

Я хочу быть в состоянии сделать это: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 #?

ответ

15

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

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> 

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

UPDATE

Основываясь на комментариях здесь некоторые ответы:

  • Этот метод использует перегрузкам, разрешаемые при компиляции, так что не снижает производительность во время выполнения. Он основан на том, что я написал некоторое время назад в my blog.

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

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

  • Phil Trelford's technique (упомянутый в ответе Рида) работает во время выполнения, третьим вариантом будет использование фантомных типов, для этого может потребоваться меньше хаков.

Заключение

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

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

а затем определить те функции преобразования я бы использовать перегруженные, но было бы меньше, чем Hacky заново определяя Exis бинарный оператор.

+0

Вау, это здорово! У меня есть несколько вопросов: 1. Почему вы не рекомендуете его? 2. Не освобождайте ли я тип безопасности? 3. Если вам нужно было выбрать, вы бы предпочли пойти с решением времени работы Фила Трерфорта или этим? 4. Что на самом деле делает $? Спасибо! – user3323923

+0

Btw, я бы хотел принять два ответа. – user3323923

+0

@ user3323923, Gustavo определяет оператор '$' с помощью круглых скобок '($)'. @Gustavo, я второй вопрос, почему бы вам не использовать его? Это похоже на законное решение. –

5

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

Однако Фил Трелфорд продемонстрировал механизм, с помощью которого вы могли бы create runtime classes which do support this, хотя и с немного отличающимся синтаксисом. Используя свои типы, вы могли бы написать:

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