Джулия как расширить функцию isless

#generics #julia

#дженерики #джулия

Вопрос:

Я новичок в Джулии и только что узнал, что Джулия может генерировать общие методы, изменяя сигнатуру функции.

 function round_number(x::Float64)
  return round(x)
end

round_number(3.1415)
round_number(3)

function round_number(x::Int64)
  return x
end

round_number(3)
 

Когда я обнаружил, что функция test_interpolated не может работать с числом и строкой, я попытался посмотреть, смогу ли я сгенерировать новую подпись для расширения isless . К сожалению, это не сработало.

 function test_interpolated(a, b)
  if a < b
      "$a is less than $b"
  elseif a > b
      "$a is greater than $b"
  else
      "$a is equal to $b"
  end
end

test_interpolated(3.14, 3.14)
test_interpolated(3.14, "3.14")

function isless(x::AbstractFloat, y::AbstractString)
  return x < float64(y)
end

test_interpolated(3.14, "3.14")
 

Неужели я неправильно понял эту концепцию?

Ответ №1:

Сначала обратите внимание, что float64 в Julia нет функции. Чтобы разобрать строку как число, вам нужно использовать parse функцию:

 parse(Float64, "3.14")
 

Вторая вещь в вашем коде заключается в том, что:

 function isless(x::AbstractFloat, y::AbstractString)
  return x < float64(y)
end
 

создает новую функцию с именем isless в вашем текущем модуле (скорее Main всего, если вы работаете, например, в REPL).

Чтобы добавить метод к isless функции, определенной в Base модуле, вам нужно будет написать:

 function Base.isless(x::AbstractFloat, y::AbstractString)
  return x < float64(y)
end
 

(обратите внимание на Base. префикс)

Хотя Джулия допускает это, вы никогда не должны делать это в своем коде. Этот стиль называется пиратством типов и объясняется здесь .

Короче говоря, вы добавляете метод к функции из Base модуля AbstractFloat , и оба AbstractString они также определены в Base модуле. Проблема в том, что добавление такого метода может привести к поломке другого кода, который в этом случае предполагает, что isless при передаче AbstractFloat и AbstractString выдает ошибку.

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

 toreal(x::Real) = x

toreal(x::AbstractString) = parse(Float64, x)

function test_interpolated(a::Real, b::Real)
  if a < b
      "$a is less than $b"
  elseif a > b
      "$a is greater than $b"
  else
      "$a is equal to $b"
  end
end

test_interpolated(a, b) = test_interpolated(toreal(a), toreal(b))
 

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

Обратите внимание, что я использую Real not AbstractFloat , поскольку сравнение с < гарантированно определяется для двух Real значений, и вы, скорее всего, хотите, чтобы ваша функция, например, принимала целые числа.

Подход, который я показываю, объясняется здесь .


Редактировать

Пример неоднозначности отправки. Используйте свежий сеанс Julia 1.7.0:

 julia> struct T end

julia> Base.propertynames(::T, ::Integer) = nothing

julia> propertynames(T(), true)
ERROR: MethodError: propertynames(::T, ::Bool) is ambiguous. Candidates:
  propertynames(x, private::Bool) in Base at reflection.jl:1666
  propertynames(::T, ::Integer) in Main at REPL[2]:1
 

Комментарии:

1. Просто еще один пример того, как пиратство типов (часто) нарушает отправку методов: представьте пакет, который использует typecode(::AbstractString) = 2 константу времени компиляции, затем вы определяете typecode(::String) = 16 . Этот пакет теперь сломан для String s. Защита от непреднамеренного пиратства типов — это именно то, почему определение isless вместо Base.isless в вашем модуле создает новую функцию. Разработчики знают, что никто не может знать все имена в Base, поэтому эта система позволяет вам использовать любое имя, которое вы хотите, содержащееся в вашем модуле, а другие модули могут использовать его только путем явного импорта.

2. Значит, пиратство типов поддерживается пользователем Julia?

3. Это скорее групповое усилие, просто как минимум 2 разных человека с разными намерениями случайно пишут методы, которые конфликтуют. Вы можете избежать столкновения методов самостоятельно с помощью простого руководства: 1) если ни один из аргументов не относится к созданным вами типам, создавайте и расширяйте только созданные вами функции, 2) если некоторые аргументы относятся к созданным вами типам, вы можете расширить чужую функцию. Кроме того, несколько разработчиков могут совместно редактировать свои библиотеки для устранения конфликтов; это также называется пиратством типов, но это хороший вид.

4. Действительно, последний комментарий BatWannaBe является актуальным случаем, требующим сотрудничества. Например. некоторые функции Base имеют свободную параметризацию. Затем, когда вы добавляете методы к таким функциям для своих типов, вы можете непреднамеренно ввести неоднозначность отправки метода. Примером является Base.propertynames(::T, ::Integer) = nothing то, где T находится ваш пользовательский тип. Такое определение создает неоднозначность отправки, хотя вы использовали свой тип T в подписи. Это можно исправить, добавив Base.propertynames(::T, ::Bool) дополнительно в этом случае, но я просто хочу показать вам, что вам нужно быть осторожным.

5. Спасибо вам обоим за добрые предложения и советы :). Я больше углублюсь в Джулию.