#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. Спасибо вам обоим за добрые предложения и советы :). Я больше углублюсь в Джулию.