Hello,
I have a question concerning generics / function overloading. Say I have a (somewhat silly for demonstration purposes) "module A". The main function quotedPair takes an argument and returns a tuple containing the argument itself as well as some sort of "quoted" form of the argument which is generated using a quote function. I provide an implementation of quote for strings but I want to give users of my module the possibility to extend quotedPair, hence quotedPair is a generic function.
# moda.nim
proc quote*(s: string): string =
"`" & s & "`"
proc quotedPair*[T](x: T): (T, string) =
(x, x.quote)
But if I try to use module A and extend it with an implementation for integers, it does not work:
# modb.nim
import moda
proc quote(s: int): string =
# reusing the quote implementation for strings here
quote($s)
# this works
echo quotedPair("hello")
# this is a compile time error
echo quotedPair(1)
I get a compile time error, although I provide an implementation of quote for integers.
../moda.nim(8, 10) Error: type mismatch: got <int>
but expected one of:
proc quote(s: string): string
first type mismatch at position: 1
required type for s: string
but expression 'x' is of type: int
But what I don't understand: As soon as I add some generic version of quote to module A (that does not even has to match int) the code does compile. Like so:
# moda.nim (modified)
# this doesn't match for `x: int`!
proc quote*[T](x: seq[T]): string = ""
proc quote*(s: string): string =
"`" & s & "`"
proc quotedPair*[T](x: T): (T, string) =
(x, x.quote)
What is happening here and is that expected? Also, could I solve my problem without the generic (dummy) implementation, maybe somehow 'annotating' the quote function to be extendable?
By the way, I spotted this technique in Nim mustache. There is a Context type to which "values" can be added. If the value is not the correct type Value, it gets cast automatically, see:
https://github.com/soasme/nim-mustache/blob/master/src/mustachepkg/values.nim#L149
Nim mustache itself provides some implementations of castValue, but they are talking explicitly about writing your own implementations for custom types. This only seems to work because there are some generic implementations of castValue that are provided by Nim mustache.
In addition to what @shirleyquirk said, if the function you're defining sees multiple implementations of quote, it automatically becomes an open symbol, so that's why the seemingly unrelated definition caused a difference.
Also, be careful with the name because there's a quote macro in std/macros. May I suggest enquote (inspired by LaTeX)?
Thank you very much, that was exactly the piece of information I was missing! Also, I see now that it's right in the documentation: https://nim-lang.org/docs/manual.html#generics-mixin-statement
On a sidenote: yeah, I know about quote from std/macro. I don't need a quote function like in my example but I find it sometimes hard to come up with good examples. But should I ever need it, enquote is a welcome suggestion.
I've done some more experiments and I have a follow up question. I changed my module A to include a somewhat less dummy implementation of quote[T] for seq[T]:
# moda.nim
import sequtils
import strutils
proc quote*(s: string): string =
"`" & s & "`"
proc quote*[T](x: seq[T]): string =
x.map(quote).join(",")
proc quotedPair*[T](x: T): (T, string) =
(x, x.quote)
The quote[T](x: seq[T]) function will only work for strings if the quote(x: string) is implemented before the generic version (or with code reordering enabled).
But as a consequence, quote[T](x: seq[T]) does not work for my custom implementation (regardless of code reordering enabled):
# modb.nim
import moda
proc quote(s: int): string =
quote($s)
# this works:
echo quotedPair(1)
# this doesn't work:
echo quotedPair(@[1])
It makes sense that it does not work but I think it would be be nice if it worked. Is there a way to achieve this? This also is a problem when you implement a custom castValue for Nim mustache. Of course you can implement quote(x: seq [int]) to make it work or even copy quote[T](x: seq[T]) verbatim to modb.nim (just put it after quote(s: int)).