I have previously been conflicted on what I think are two general kinds of APIs in julia. For the sake of an example, take the sort function:
sort(v; alg::Algorithm=defalg(v), lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward)
I will focus on the lt
and by
arguments. Another possible API would be to not have those keyword arguments, and just define the appropriate methods on some user-defined type. As a generic example:
struct SortWrap{By, IsLess, T}
value::T
end
function Base.isless(a::S, b::S) where {T, By, IsLess, S <: SortWrap{By, IsLess, T}}
return IsLess(By(a.value), By(b.value))
end
# allow SortWrap{By, IsLess}(value)
function (::Type{SortWrap{By, IsLess, T} where T})(value) where {By, IsLess}
T = typeof(value)
SortWrap{By, IsLess, T}(value)
end
Then we can use a custom “by” and “isless” as follows:
data = rand(1:10, 10)
sort(SortWrap{identity, isless}.(data))
sort(SortWrap{Base.:-, isless}.(data))
I have not benchmarked to see if there is any performance penalty. But assuming there isn’t a significant performance difference, is there any reason to prefer one approach over the other?
One vague sense I have is driven by the recognition that there is no matrix multiplication function in julia that takes in add
and multiply
arguments. Instead, you define a new number type with the appropriate scalar +
and *
methods. There also isn’t much sense in defining a MatMulWrap
struct with parametrizable +
and *
operations.