Оператор перегрузки в F#: (/)

Я хотел бы перегрузить оператор (/) в F# для строк и сохранить значение для чисел.

/// Combines to path strings
let (/) path1 path2 = Path.Combine(path1,path2)

let x = 3 / 4 // doesn't compile

Если я попробую следующее, я получу: "Предупреждение 29 Члены расширения не могут обеспечить перегрузки операторов. Попробуйте вместо этого определить оператор как часть определения типа".

/// Combines to path strings
type System.String with
  static member (/) (path1,path2) = Path.Combine(path1,path2)

Есть идеи?

С уважением, форки

4 ответа

Решение

Вы не можете предоставить перегруженные операторы для существующих типов. Одним из вариантов является использование другого имени оператора (как предполагает Natahan). Тем не менее, вы также можете определить новый тип для представления путей в вашем коде F# и предоставить / Оператор для этого типа:

open System    

// Simple type for representing paths
type Path(p) =
  // Returns the path as a string
  member x.Path = p 
  // Combines two paths
  static member ( / )(p1:Path, p2:Path) = 
    Path(IO.Path.Combine(p1.Path, p2.Path))

let n = 4 / 2
let p = Path("C:\\") / Path("Temp")

Это имеет одно важное преимущество - делая типы более явными, вы предоставляете контролеру типов больше информации, которую он может использовать для проверки вашего кода. Если вы используете строки для представления путей, вы можете легко спутать путь с какой-либо другой строкой (например, именем). Если вы определите свой Path type, средство проверки типов не позволит вам совершить эту ошибку.

Более того, компилятор не позволит вам (просто) неправильно комбинировать пути (что может легко произойти, если вы представляете пути в виде строк), потому что p + p не определено (вы можете использовать только /, который правильно использует Path.Combine).

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

Можно взломать что-то вместе, что сработает, но это очень некрасиво:

type DivisionOperations =
  static member Divide(x:int, y:int) = x / y
  static member Divide(path1, path2) = Path.Combine(path1, path2)

let inline div< ^t, ^a, ^b, ^c when (^t or ^a) : (static member Divide : ^a * ^b -> ^c)> a b = ((^t or ^a) : (static member Divide : ^a * ^b -> ^c) (a, b))

let inline (/) x y = div<DivisionOperations, _, _, _> x y

На самом деле вы можете.

Попробуй это:

open System.IO

type DivExtension = DivExtension with
    static member inline (=>) (x             , DivExtension) = fun y -> x / y
    static member        (=>) (x             , DivExtension) = fun y -> Path.Combine(x, y)
    static member        (=>) (x:DivExtension, DivExtension) = fun DivExtension -> x

let inline (/) x y = (x => DivExtension) y

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

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

let (</>) path1 path2 = Path.Combine (path1,path2)

Это, вероятно, будет менее раздражающим в конечном счете, потому что это не портит неявный вывод типа, который выполняет читатель-человек./ означает, что результатом является число с плавающей запятой, и помнить, что это иногда строка, является бременем *. Но после первого раза читатель видит </>Легко запомнить, что он делает что-то, связанное с символом, встроенным в середину.

* Я думаю, что единственная причина + для строк выглядит нормально, это передержка. После длительного использования Haskell или Caml первые несколько минут после переключения на другой язык "foo" + "bar" выглядит ужасно плохо.

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