Оператор перегрузки в 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)

Любые идеи?

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


person forki23    schedule 11.05.2010    source источник
comment
Вы пытались определить его как op_Division? ОБНОВЛЕНИЕ: неважно; это не сработает   -  person pblasucci    schedule 11.05.2010
comment
Я не думаю, что вы можете перегружать члены (или операторы) уже существующих классов.   -  person Gabe    schedule 11.05.2010


Ответы (4)


Вы не можете предоставлять перегруженные операторы для существующих типов. Один из вариантов — использовать другое имя оператора (как предлагает Натахан). Однако вы также можете определить новый тип для представления путей в коде 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, средство проверки типов предотвратит эту ошибку.

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

person Tomas Petricek    schedule 11.05.2010
comment
Приятно, когда на вопрос отвечает человек, написавший книгу по теме! - person Jose Basilio; 11.05.2010
comment
Хм, а как насчет синтаксиса в этом духе: (пусть p = Path C:\\/Temp), предоставляя перегрузку p1:Path, P2:string? - person Rick Minerich; 17.02.2012

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

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

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
person Gus    schedule 13.12.2011
comment
Обратите внимание, что на самом деле это очень похоже на мой ответ, хотя, добавив фиктивную перегрузку DivExtension * DivExtesion -> DivExtension -> DivExtension, вы смогли использовать обобщение первой перегрузки, не позволяя компилятору по умолчанию использовать оператор (/) для второй перегрузки, что очень умно. Я чувствую, что использование вами символического оператора (=>) затрудняет понимание тела (/), хотя вы можете опустить явные ограничения статического члена. - person kvb; 13.12.2011
comment
Да, ты прав. Я предпочитаю использовать промежуточные операторы, чтобы избежать написания статических ограничений. В свой ответ вы также можете добавить фиктивную перегрузку, и она также будет обобщаться. Одна вещь, которую я не могу сделать без операторов, — это написать дважды «или», я имею в виду что-то вроде «когда» (^t или ^a или ^b). В этих случаях я использую тернарный оператор, я думаю, что это единственный способ. - person Gus; 14.12.2011
comment
Привет, я проверил, и это работает, но я действительно не понимаю, почему (я новичок в F#)... - person Liviu; 17.02.2014
comment
@Liviu Это основано на перегрузке. Этот метод объясняется здесь nut-cracker.com.ar. - person Gus; 17.02.2014
comment
nut-cracker.com.ar не отвечает, кажется, он переместился на: nut-cracker.azurewebsites.net - person JJJ; 06.03.2015
comment
@JJJ Да, он переехал туда. Спасибо. - person Gus; 06.03.2015
comment
@Gustavo Я пытаюсь понять оператор объединения null, определенный здесь: stackoverflow.com/a/21194566/5547, который ссылки на этот вопрос. Ваш способ определения вещей выглядит проще, за исключением того, что я ничего не могу найти об операторе (=›), у вас есть ссылка на его определение? - person JJJ; 06.03.2015
comment
@ Густаво, неважно, это я тупой, ты, конечно, определяешь оператора. Прости - person JJJ; 06.03.2015
comment
@JJJ да, это просто произвольный оператор. Может сбить с толку на первый взгляд. - person Gus; 06.03.2015
comment
@Gustavo: Это очень круто, но третий статический элемент кажется мне черной магией. Кажется, что это сводится к функции тождества (id<DivExtension>), которая фактически не работает. Зачем это нужно? Спасибо. - person brianberns; 22.08.2018
comment
@brianberns Это необходимо для создания необходимой двусмысленности между перегрузками. В противном случае он попытается разрешить их заранее (попробуйте удалить). - person Gus; 23.08.2018
comment
@Густаво Спасибо. Я понимаю, что вы имеете в виду, когда я удаляю его. Тем не менее, я до сих пор не понимаю, почему для создания двусмысленности требуется третий член, и почему эта конкретная подпись члена заставляет оператор (/) обобщать желаемое. - person brianberns; 23.08.2018
comment
Да, алгоритм разрешения перегрузки сложнее, чем думает большинство людей. Вы можете взглянуть на спецификацию F#, но я думаю, что компилятор F# не на 100% соответствует этой спецификации. В основном, когда метод выигрывает, он разрешается этому. Создание еще одного метода с удобной сигнатурой делает алгоритм разрешения конфликтов неспособным принять решение, а это то, что нам нужно в данном случае. - person Gus; 23.08.2018
comment
@ Густаво А, теперь понятно. Я думал, что в этой подписи есть что-то особенное, но похоже, что любой третий участник создаст необходимую двусмысленность. Например, такая подпись тоже работает: static member (=>) (x : char, DivExtension) - person brianberns; 23.08.2018

Я не думаю, что есть простой способ сделать это. Члены расширения не учитываются при перегрузке операторов в 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
person kvb    schedule 11.05.2010

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

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

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

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

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

person Nathan Shively-Sanders    schedule 11.05.2010
comment
Я использую (@@) в данный момент, но я думаю, что (/) было бы лучше. - person forki23; 11.05.2010