function CalcVIR(v::Union{Number,Missing},
i::Union{Number,Missing},
r::Union{Number,Missing})
''' If voltage is missing then calc v = i * r
If current is missing then calc i = v / r
If resistance is missing then calc r = v / i
'''
if ismissing(v)
return i * r
elseif ismissing(i)
return v / r
elseif ismissing(r)
return v / i
end
end

And using it like

Starting Julia...
_
_ _ _(_)_ | Documentation: https://docs.julialang.org
(_) | (_) (_) |
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.
| | | | | | |/ _ | |
| | |_| | | | (_| | | Version 1.2.0 (2019-08-20)
_/ |\__ _|_|_|\__ _| | Official https://julialang.org/ release
|__/ |
julia> v = CalcVIR(missing,3,5)
15
julia> v = CalcVIR(missing,3.4,5.6)
19.04
julia> i = CalcVIR(7,missing,3)
2.3333333333333335

This doesn’t feel idiomatic to me since you’ll need to keep track what will be returned. I would probably assign the missing value and then return v, i, r

complete_vir(v::Missing, i::Real, r::Real) = (v = i * r, i = i, r = r)
complete_vir(v::Real, i::Missing, r::Real) = (v = v, i = v / r, r = r)
complete_vir(v::Real, i::Real, r::Missing) = (v = v, i = i, r = v / i)

This way, it would be easier to avoid bugs from using the wrong variable accidentally from the output.

function CalcVIR(;v=missing, i=missing, r=missing)
!ismissing(i) && !ismissing(r) && return i * r # i, r present
!ismissing(v) && !ismissing(r) && return v / r # v, r present
!ismissing(v) && !ismissing(i) && return v / i # v, i present
throw("Missing inputs ..") # 1 or 2 inputs missing
end

and test different inputs:

julia> v = CalcVIR(i=3, r=5)
15
julia> i = CalcVIR(v=15, r=5)
3.0
julia> r = CalcVIR(v=15, i=3)
5.0
julia> v = CalcVIR(i=3)
ERROR: "Missing inputs .."
Stacktrace:
[1] #CalcVIR#13(::Missing, ::Int64, ::Missing, ::Function) at .\REPL[32]:5
[2] (::getfield(Main, Symbol("#kw##CalcVIR")))(::NamedTuple{(:i,),Tuple{Int64}}, ::typeof(CalcVIR)) at .\none:0
[3] top-level scope at none:0

The keyword inputs are named, so it is obvious that the missing one is being returned. I agree, however, that using a named tuple for outputs can be better.

Something like this:

function CalcVIR(;v=missing, i=missing, r=missing)
!ismissing(i) && !ismissing(r) && return (v = i * r, i = i, r = r) # i, r present
!ismissing(v) && !ismissing(r) && return (v = v, i = v / r, r = r) # v, r present
!ismissing(v) && !ismissing(i) && return (v = v, i = i, r = v / i) # v, i present
throw("Missing inputs ..") # missing 1 or 2 inputs
end

Voting for @Tamas_Papp’s solution.
Maybe even more idiomatic is to put such things into a struct that would always contain consistent values:

struct VoltCurRes{T<:Number}
voltage::T
current::T
resistance::T
function VoltCurRes(volt::V, cur::I, res::R) where {V<:Number, I<:Number, R<:Number}
struct_type = reduce(promote_type, (V, I, R))
new{struct_type}(volt, cur, res)
end
end
VoltCurRes(volt::Missing, cur::Number, res::Number) = VoltCurRes(cur * res, cur, res)
VoltCurRes(volt::Number, cur::Missing, res::Number) = VoltCurRes(volt, volt / res, res)
VoltCurRes(volt::Number, cur::Number, res::Missing) = VoltCurRes(volt, cur, volt / cur)

Update: While the logic here is almost the same as with ismissing() checks, there is at least one logical and one performance-relevant difference.
The logical difference is, the functions as written by Tamas and myself explicitly require exactly one missing argument and will give an error if there are more.
The performance difference is that the type matching can be done at compile time while the ismissing() checks have to be done at runtime, which makes the version employing multiple dispatch faster in some cases.

because in Electrical Engineering, the Voltages can be Real or Complex.

In fact having Imaginary Voltages is PERFECTLY normal in Electrical Engineering.

So the type Number is highly suitable for Electrical Engineering. This is why I use the type Number instead of the type Real or AbstractFloat in my julia code for the function CalcVIR.

i saw this on a matlab program to calculate psycrometric properties for humid air. in that case they take the parameters as keywords and returned all variables (calculated and specified). its used a lot in chemical engineering simulation. here is my two cents on how to do it:

#main function: the user should only see calculate_vir,
#and not use the implementation calc_vir
calculate_vir(;v=missing;i=missing;r=missing) = calc_vir(v,i,r)
calc_vir(v::Missing, i::Number, r::Number) = (i*r,i,r)
calc_vir(v::Number, i::Missing, r::Number) = (v,v/r,r)
calc_vir(v::Number, i::Number, r::Missing) = (v,i,v/i)

Here is an interesting case, you can throw an error (overspecified), or try to calculate using some values (the cheapest calculation), it depends if your calculation comes from a system or it is user specified

#this comes from user overspecification
calc_vir(v::Number, i::Number, r::Number) = error("system overspecified")
#in this case, a tuple comes from a system calculation, so its reasonable to return the same tuple.
#you can add some checks if you want to be sure, in this case v≈i*r
calc_vir(vir::Tuple{Number,Number,Number}) = vir