Соответствие шаблону F # для типов кортежей - PullRequest
10 голосов
/ 06 февраля 2010

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

type MyType1 = A | B of float
type MyType2 = C | D of int

То, что я пытался сделать, это:

let func x y =
    match (x, y) with
    | :? Tuple<MyType1, MyType1> -> "1, 1"
    | _ -> "..."

Однако это невозможно. F # жалуется:

Тип '' a * 'b' не имеет надлежащих подтипов и не может использоваться в качестве источника проверки типа или приведения во время выполнения.

Какой элегантный способ сделать это?

РЕДАКТИРОВАТЬ : Позвольте мне попытаться уточнить это.

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

То есть вместо предоставления:

let op11 (x : MyType1) (y : MyType1) = // do something useful
let op12 (x : MyType1) (y : MyType2) =
    // convert y to MyType1
    let y' = // ...
    // dispatch to op11
    op11 x y'
let op21 (x : MyType2) (y : MyType1) = // similar
let op22 (x : MyType2) (y : MyType2) = // similar

я хотел бы представить одну функцию клиентскому коду:

let op (x : obj) (y : obj) = // ...

Это похоже на имитацию поведения перегрузки метода, но с каррированными функциями.

Ответы [ 4 ]

15 голосов
/ 06 февраля 2010

Ваш код не работает, потому что F # обобщает тип аргументов для параметра типа. Я думаю, что вы не можете динамически проверить, можно ли преобразовать тип 'a * 'b в тип MyType1 * MyType2 (хотя это немного смущает меня). В любом случае вы можете написать функцию, которая принимает два аргумента типа obj и проверяет их отдельно, используя два шаблона :?:

type MyType1 = A | B of float 
type MyType2 = C | D of int

let func (x:obj) (y:obj) = 
    match (x, y) with 
    | (:? MyType1 as x1), (:? MyType1 as x2) -> 
        printfn "%A %A" x1 x2
    | _ -> 
        printfn "something else" 

func A (B 3.0) // A B 3.0
func A (D 42)  // something else

В любом случае, было бы интересно узнать, почему вы хотите это сделать? Там может быть лучшее решение ...

РЕДАКТИРОВАТЬ (2) Итак, из всех 4 двухэлементных комбинаций T1 и T2 вам нужна функция, которая может принимать 3. Это правильно (T1 * T1, T1 * T2 а T2 * T2)? В этом случае вы не можете написать полностью безопасную функцию карри, потому что тип второго аргумента будет «зависеть» от типа первого аргумента (если первый аргумент имеет тип T2, то второй аргумент также должен быть T2 (в противном случае это может быть T1 тоже)).

Вы можете написать безопасную функцию без каррирования, которая принимает аргумент следующего типа:

type MyArg = Comb1 of T1 * T1 | Comb2 of T1 * T2 | Comb3 of T2 * T2

Тип функции будет MyArg -> string. Если вам нужна функция с карри, вы можете определить тип, который позволит вам использовать либо T1, либо T2 в качестве первого и второго аргумента.

type MyArg = First of T1 | Second of T2

Тогда ваша функция карри будет MyArg -> MyArg -> string. Но обратите внимание, что если не разрешена одна комбинация типов аргументов (если я вас правильно понимаю, T2 * T1 не должно быть разрешено). В этом случае ваша функция просто должна будет сгенерировать исключение или что-то в этом роде.

6 голосов
/ 06 февраля 2010

Существуют три различных способа сделать это.

Во-первых, нужно пожертвовать статической типизацией, повысив значение до obj, как вы предложили:

let func x y =
  match box x, box y with
  | (:? MyType1 as x), (:? MyType1 as y) ->
      ...

Это практически всегда ужасное решение, поскольку оно приводит к введению ненужных проверок типов во время выполнения:

  | _ -> invalidArg "x" "Run-time type error"

Место only , которое я хорошо видел в этой работе, - это библиотечный код, разработанный специально для пользователей, чтобы вызывать из интерактивных сеансов F #, где ошибки типа времени компиляции и времени выполнения эффективно происходят одновременно, и динамическая типизация может быть более кратким. Например, наша библиотека F # для визуализации позволяет пользователю попытаться визуализировать любое значение любого типа, используя эту технику, чтобы вызывать пользовательские процедуры визуализации для различных (известных) типов ( read more ) .

Второй - заменить два разных типа одним типом:

type MyType1 = A | B of float | C | D of int   

Третье решение - ввести новый тип, который объединяет только эти два типа:

type MyType = MyType1 of MyType1 | MyType2 of MyType2
3 голосов
/ 06 февраля 2010

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

Эта функция должна решить, какая из opXY для вызова, должным образом убитый типы.

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

С моей точки зрения, если ваши типы очень похожи, они должны быть объединены в один тип:

type MyType = A | B of float | C | D of int

За исключением этого, вы можете заключить два типа в другой тип:

type composite =
    | MyType1Tuple of MyType1 * MyType1
    | MyType2Tuple of MyType2 * MyType2

Еще один слой косвенности никогда никому не вредил. Но, по крайней мере, теперь ваши клиенты могут обернуть объекты другим, не теряя при этом безопасность типов.

И, исключая все это, предоставьте два разных метода для ваших разных типов.

1 голос
/ 06 февраля 2010

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

type MyType1 = A | B of float 
type MyType2 = C | D of int 

// imagine this adds floats, and C -> A (=0.0) and D -> B
let DoIt x y =
    match x, y with
    | A, A -> 0.0
    | A, B z -> z
    | B z, A -> z
    | B z1, B z2 -> z1 + z2

let Convert x =
    match x with
    | C -> A
    | D i -> B (float i)

let Func (x:obj) (y:obj) =
    match x, y with
    | (:? MyType2 as xx), (:? MyType2 as yy) -> DoIt (Convert xx) (Convert yy)
    | (:? MyType1 as xx), (:? MyType2 as yy) -> DoIt xx (Convert yy)    
    | (:? MyType2 as xx), (:? MyType1 as yy) -> DoIt (Convert xx) yy
    | (:? MyType1 as xx), (:? MyType1 as yy) -> DoIt xx yy    
    | _ -> failwith "bad args"
...