This is indeed probably the best possibility in the case of the small example. But now imagine a function that takes ten parameters, does some preprocessing, possibly postprocessing, etc… And one or two of the parameters are optional in the sense of null-passing. Relying on dispatch in this case is of course also possible, but would then involve lots of code duplication.
Let me give two concrete examples. One is a bug report that I just filed with Mosek, where they relied on the proposed behavior that nothing
is implicitly converted to C_NULL
(which I would argue is quite a natural assumption). The C-Interface looks like
MSKrescodee (MSKAPI MSK_appendacc) (
MSKtask_t task,
MSKint64t domidx,
MSKint64t numafeidx,
const MSKint64t * afeidxlist,
const MSKrealt * b)
The length of the last two arrays is given by numafeidx
, and b
can be NULL
if it is not present. The wrapper function does some preprocessing on the indices (convert one-based to zero-based) and retrieves the value for numafeidx
from the length of the vectors. The Julia signature is
function appendacc(task::MSKtask,domidx::Int64,afeidxlist::Vector{Int64},b::Union{Nothing,Vector{Float64}})
This was a “small” example. I recently wrote a Julia wrapper for the Fortran function LANCELOT_simple
from the GALAHAD library. The Fortran interface looks like this:
SUBROUTINE LANCELOT_simple( n, X, MY_FUN, fx, exit_code, &
MY_GRAD, MY_HESS, &
BL, BU, VNAMES, CNAMES, neq, nin, CX, Y, &
iters, maxit, gradtol, feastol, print_level )
INTEGER ( KIND = ip_ ), INTENT( IN ) :: n
INTEGER ( KIND = ip_ ), INTENT( OUT ) :: exit_code
REAL ( KIND = rp_ ), INTENT( INOUT ) :: X( : )
REAL ( KIND = rp_ ), INTENT( OUT ) :: fx
CHARACTER ( LEN = 10 ), OPTIONAL :: VNAMES( : ), CNAMES( : )
INTEGER ( KIND = ip_ ), OPTIONAL :: maxit, print_level
INTEGER ( KIND = ip_ ), OPTIONAL :: nin, neq, iters
REAL ( KIND = rp_ ), OPTIONAL :: gradtol, feastol
REAL ( KIND = rp_ ), OPTIONAL :: BL( : ), BU( : ), CX( : ), Y( : )
OPTIONAL :: MY_GRAD, MY_HESS
There are tons of optional arrays in here (to make it worse, assumed-shape, but that’s a different matter entirely). There are also a couple of callbacks and references which make preprocessing necessary. My Julia function signature looks like
function LANCELOT_simple(n::Integer, X::AbstractVector{Cdouble}, MY_FUN::Function; MY_GRAD::Union{Function,Missing}=missing,
MY_HESS::Union{Function,Missing}=missing, BL::Union{<:AbstractVector{Cdouble},Nothing}=nothing,
BU::Union{<:AbstractVector{Cdouble},Nothing}=nothing, neq::Integer=0, nin::Integer=0,
CX::Union{<:AbstractVector{Cdouble},Nothing}=nothing, Y::Union{<:AbstractVector{Cdouble},Nothing}=nothing,
maxit::Integer=1000, gradtol::Real=1e-5, feastol::Real=1e-5, print_level::Integer=1)
and indeed, there’s a lot of (unnecessary) clutter due to isnothing
/ismissing
checks in my ccall (I used nothing
in the semantic sense that there is no boundary, and missing
in the sense that while these things for sure exist, they need to be constructed manually or we are not interested in their outputs).