When the argument of a function has a default value, even if specify a value for the argument at the time of the call, the process will sometimes proceed using the default value, as shown below:
(The first half is the implementation of the function, and the actual process call is the last line.)
proc sortAsc[T](x: T, y: T): int =
# compare using operand `<` and `==`
if x < y: -1
elif x == y: 0
else: 1
proc advancedSort[T](arr: openArray[string], translator: string -> T, sortAscFunc: (T, T) -> int = sortAsc): seq[string] =
# sort by what you'd like types from string
var translated =
collect(newSeq):
for elm in arr:
translator(elm)
translated.sort(sortAscFunc)
result =
collect(newSeq):
for elm in translated:
$elm
proc fromStringToComplex(c: string): Complex[float] =
# translate from string to complex
# ex) "(1.0,2.0)" ---> 1.0 + 2.0j
var matches: array[2, string]
let matchOk = match(c, re"\((\d+(?:\.\d)?),(\d+(?:\.\d)?)\)", matches)
if matchOk:
result = complex(
parseFloat(matches[0]),
parseFloat(matches[1]),
)
else:
result = complex(0.0,0.0)
proc sortComplexAsc(x, y: Complex[float]): int =
# compare absolute value of x and absolute value of y
if abs(x) < abs(y): -1
elif abs(x) == abs(y): 0
else: -1
########
var arr = ["(1.0,2.0)","(5.0,6.0)","(2.0,3.0)"]
# the below will be compile error
# like "there is no operand `<` of Complex[float]"
arr.advancedSort(translator=fromStringToComplex, sortAscFunc=sortComplexAsc)
What this sample wants to do is to convert a sequence with elements represented by string values to an arbitrary type and sort it. But this sample will raise a compile error like "there is no operand < of Complex[float]". What this error is saying is that the value passed to argument translator is reflected, but the value passed to argument sortAscFunc uses the default value; (generic type T is interpreted as Complex[float]).
In this sample, I believe it is correct that sortComplexAsc is passed as argument sortAscFunc.
Please tell me if this is a bug (or specifications) or if I am mistaken?
it's not a bug, and it's not using the default compare, but the compiler does have to emit code for that compare function.
(incidentally, why not use system.cmp ?)
you can demonstrate that your code isn't using the default value by defining:
proc `<`[T](x,y:Complex[T]):int = raise new ValueError
and seeing that no exception is raisedto @shirleyquirk
Thank you for the clear explanation. I tried it and can see there is no exception, so the value specified at the time of the call (sortComplexAsc) is correctly assigned to argument sortAscFunc, isn't it?
However, if don't define a function to raise this exception (proc `<`[T](x,y:Complex[T]):int = raise new ValueError), the compile error will still exist. Why does the compiler evaluate the comparison operator (if x < y: -1) of the default function (sortAsc) when the possibility of the default function being called should be zero since I have assigned a different value to the argument sortAscFunc?
Why does the compiler evaluate
Good question, and I struggled to find a compelling answer; i think it boil down to, 'because it's designed that way' . Instantiation proceeds one line at a time, and at the time when that proc is instantiated, the compiler has every reason to believe that you might call it without the default argument sometime later, so it goes ahead and instantiates it too.
Could default values wait to be instantiated until such time as they are needed? sure, but I don't think that feature will be added, because you can get lazy evaluation by changing advancedSort from a proc to a template:
template advancedSort[T](arr: openArray[string], translator: string -> T, sortAscFunc: (T, T) -> int = sortAsc): seq[string] =
# sort by what you'd like types from string
var translated =
collect(newSeq):
for elm in arr:
translator(elm)
translated.sort(sortAscFunc)
collect(newSeq):
for elm in translated:
$elm
fyi, since 1.6.0 collect infers the collection type, so you can use this syntax:
collect(for elm in translated:
$elm
Why does the compiler evaluate
Here is my answer to your question. But it can be wrong. I'm not an expert of Nim compiler.
When you use a generic proc, compiler checks if the generic proc can be instantiated with given generic parameters. If that check depends on other factors like whether it uses default param or not, using generic proc without compile error would be complicated. That means when you use a generic proc, you need to know not only generic parameters that work with the generic proc, but also need to know default parameter may not be used when given generic param type doesn't have specific proc.
(n is a number of lines of input source code)
If compiler used a O(n*n) algorithm, compile time increase 4x when it got a 2x longer source code. That means using a such compiler in large project tooks very long time to compile.
I don't know much about how Nim compiler works, but checking every generic proc instantiation might take O(n*n) times. Time complexitiy of 1 generic proc instantiation probably be O(l) where l is number of lines of generic proc definition. Then time complexity of doing instantiation check for every generic proc call site is O(l * m) where m is number of generic proc call. It is almost O(n * n), if l and m are almost proportional to the number of lines of a source.
to @shirleyquirk
Thanks, all problems seem to be solved. I'll now take care when using default arguments of generic types. (incidentally, I'll adopt cmp as default comparison function, as you suggested)
to @demotomohiro
Thank you for explaining the convenience on the compiler side as well.