Generic type is not estimated when generic type T is in another generic type U, like this:
type TypeOrSeqType[U] = U|seq[U]
proc f[T](x: TypeOrSeqType[proc(y:string):T]) =
discard
let p = proc(y:string): int = parseInt(y)
f(p) #compile error: cannot instantiate: 'T'
This is just solved to rewrite type of argument x to proc(y:string):T|seq[proc(y:string):T], but if really need to use typedef TypeOrSeqType, is there any way to resolve this compile error?
(In fact, I'm going to use TypeOrSeqType for the sake of brevity, since there will be a lot of U|seq[U] forms in the code.)
The compiler is not catching this case because of the middleman that is the generic alias type (which is probably an easy fix, though a bit arbitrary). This works:
import strutils
template typeOrSeqType(U: type): type = U or seq[U]
proc f[T](x: typeOrSeqType(proc(y: string): T)) =
discard
let p = proc(y: string): int {.closure.} = parseInt(y)
f(p)
Otherwise though, in these cases where it's hard for the compiler to understand, it's possible to write small macros, usually templates are enough but this is one of the cases where the AST substitution is bottom-up and it's uglier.
import macros, strutils
type TypeOrSeqType[U] {.used.} = U | seq[U] # this definition does not change anything, TypeOrSeqType just needs to be defined to compile
macro expandTypeOrSeqType(pr) =
proc process(n: NimNode): NimNode =
result = n
for i in 1 ..< n[3].len:
let paramType = n[3][i][^2]
if paramType.kind == nnkBracketExpr and paramType[0].eqIdent("TypeOrSeqType"):
let (a, b) = (copy(n), copy(n))
a[3][i][^2] = paramType[1]
b[3][i][^2] = newTree(nnkBracketExpr, ident"seq", paramType[1])
return newStmtList(process(a), process(b))
result = process(pr)
proc f[T](x: TypeOrSeqType[proc(y: string): T]) {.expandTypeOrSeqType.} =
discard
let p = proc(y: string): int = parseInt(y)
f(p)
proc foo(a: TypeOrSeqType[int], b: TypeOrSeqType[string]) {.expandTypeOrSeqType.} = discard
foo(3, "abc")
foo(@[3], "abc")
foo(3, @["abc"])
foo(@[3], @["abc"])
Great! Thank you very much, it works fine.
In fact, what I really wanted to do is generic type estimation by default argument value, following that:
import strutils
template typeOrSeqType(U: type): type = U or seq[U]
let p = proc(y: string): int {.closure.} = parseInt(y)
proc f[T](x: typeOrSeqType(proc(y: string): T) = p) =
discard
f() #compile error: cannot instantiate: 'T'
My assumption is that since the return type of the default argument p is int, generic type T is inferred to be int. However, the compiler doesn't seem to estimate it that way.
Is there any way to resolve this compile error?
With default arguments, in the worst case you can do this:
proc f[T](x: typeOrSeqType(proc(y: string): T)) =
discard
proc f() = f(p)
The issue here is generic inference with double generic parameters. In reality, due to implicit generics, f becomes:
proc f[T; U: typeOrSeqType(proc(y: string): T)](x: U = p) =
discard
Normally U can be inferred from the default argument, but there is no mechanism to infer T from U. For example, the following works:
proc foo(a: int | string = 3) = discard
foo()
But not this:
proc foo[T; U: T](a: U = 3) = discard
foo()
Unfortunately I can't think of a good workaround other than the manual overload.
I see... Thanks for teaching me! I'm learning so much.
The conclusion is that nested estimation of generic type is not possible, at least for now? I'm very sorry to hear that, but I will substitute overload of hell for this idea. (there are many arguments of form typeOrSeqType)
Finally, my implementation was done on the form that default arguments are set by macro.
import macros
import strutils
let p = proc(y: string): int {.closure.} = parseInt(y)
let q = proc(y: string): float = parseFloat(y)
macro expandTypeOrSeqTypeWithDefault(pr) =
const defaultType = "int"
const defaultFunctionName = "p"
proc process(n: NimNode): NimNode =
result = n
for i in 1 ..< n[3].len:
let paramType = n[3][i][^2]
if paramType.kind == nnkBracketExpr and paramType[0].eqIdent("TypeOrSeqType"):
let (typ, seqTyp, defaultTyp) = (copy(n), copy(n), copy(n))
# set type of argument(normal type)
typ[3][i][^2] = copy(paramType[1])
# set type of argument(seq type)
seqTyp[3][i][^2] = newTree(nnkBracketExpr, ident"seq", paramType[1])
# set generic parameter to empty
defaultTyp[2] = newEmptyNode()
# set type of argument(default)
defaultTyp[3][i][^2] = copy(paramType[1])
defaultTyp[3][i][^2][0][0] = newIdentNode(defaultType)
# set default argument value
defaultTyp[3][i][^1] = newIdentNode(defaultFunctionName)
# return 3 types of definition of proc
return newStmtList(process(typ), process(seqTyp), process(defaultTyp))
result = process(pr)
#echo treeRepr(result)
type TypeOrSeqType[U] {.used.} = U or seq[U]
proc f[T](x2: TypeOrSeqType[proc(y: string): T]) {.expandTypeOrSeqTypeWithDefault.}=
discard
f(p) #works fine
f(q) #works fine
f() #works fine
This is very dirty implementation. We can't know what type is default value from definition of proc. So, I will quietly use overloading.