When working with higher-order functions, I often run into the following error: Error: 'f' cannot be passed to a procvar where f is some function I have defined somewhere - usually in a different module.
It is not clear to me which functions can and cannot be passed as arguments to another function, and what are the reasons behind this. Can anyone shed some light?
# first.nim
proc println*[A](a: A) = echo a
# second.nim
import first
let s = @[1, 2, 3]
proc foreach[A](xs: seq[A], f: proc(a: A)) =
for x in xs:
f(x)
s.foreach(println)
It seems to happen consistently when using procedures imported from other modules - even generic ones such as println, that are not separately compilated in the first module, but inside specialized according to the call site in second.
But I think I have seen it happen even in the same module.
The point is that I am not asking why a particular case does or does not work, or even how to solve it (easy, make a closure). I am asking what are the rules to determine whether a proc can or cannot be passed as argument to another, so that I know what to expect and write accordingly
I can't try them right now, but some guesses: If you replace A to int, then is it working? (If yes, then we need to fix paramter/types matching.) If you add {.inline.} to the end of the proc like: proc println*[A](a: A) {.inline.} = echo a, then is it working?
Cheers, Peter
Error: type mismatch: got (seq[int], proc (a: A){.inline.})
but expected one of:
example3.foreach(xs: seq[A], f: proc (a: A){.closure.})
Now, I did not specify any calling convention in foreach, and I assumed it would work for things that are not closures, in particular top level functions that do not capture anything - and in fact if the function is in the same module, it works.
From http://nim-lang.org/docs/manual.html#types-procedural-type
"Assigning/passing a procedure to a procedural variable is only allowed if one of the following conditions hold:
The rules' purpose is to prevent the case that extending a non-procvar procedure with default parameters breaks client code."
So ... the compiler just follows the spec. procvar is a debated topic (see https://github.com/nim-lang/Nim/issues/2172)
I have been able to make this work, by marking println as cdecl and then by requiring the f argument to foreach as cdecl as well.
Now, I would like foreach to be "polymorphic in the calling convention". By that I mean that - whatever f I pass to it (assuming it has an acceptable calling convention and hence can be passed as a procvar) should be acceptable. If I mark f as cdecl, I cannot pass a closure anymore.
Is there a way to write foreach so that whatever procvar is passed to it will work?
While it would be nice if the compiler could generate conversion thunks, I think the only workaround for now is to make the generic parameter more general, eg:
proc foreach[A, B](xs: seq[A], f: B) =
...
This relaxes the type checking and falls back on the compiler's semantic checking to enforce correctness.
You might also be able to use a concept:
type foreachProc[T] = concept A
type(A) is proc(t: T){.nimcall.} or
type(A) is proc(t: T){.closure.} or
type(A) is proc(t: T){.stdcall.} or
type(A) is proc(t: T){.cdecl.} or
type(A) is proc(t: T){.safecall.} or
type(A) is proc(t: T){.inline.} or
type(A) is proc(t: T){.fastcall.} or
type(A) is proc(t: T){.syscall.} or
type(A) is proc(t: T){.noconv.}
And still another way would be by using a template:
template foreachTempl(T: untyped) {.dirty.}:
proc foreach[A](xs: seq[A], f: T) =
for x in xs:
f(x)
foreachTempl(proc(t: T){.nimcall.})
foreachTempl(proc(t: T){.closure.})
foreachTempl(proc(t: T){.stdcall.})
foreachTempl(proc(t: T){.cdecl.})
foreachTempl(proc(t: T){.safecall.})
foreachTempl(proc(t: T){.inline.})
foreachTempl(proc(t: T){.fastcall.})
foreachTempl(proc(t: T){.syscall.})
foreachTempl(proc(t: T){.noconv.})
(Note: I haven't tested any of these yet.)
I have been able to make this work, by marking println as cdecl and then by requiring the f argument to foreach as cdecl as well.
Er, this has nothing to do with procvar, but with the calling conventions in general. The default is closure for a reason: It is the 'most' compatible calling convention, accepting both nimcall and closure. Furher flexibility can only be provided via generics/templates. There are strong technical reasons for this, every natively compiled language pretty much has the same restriction here.
The question is, though, why a non-procvar procedure isn't auto-converted to a closure when a closure is being expected. This is generally easy and can also be done with a macro, e.g.:
import macros
macro closure*(p: proc): untyped =
let pname = p
let ptype = p.getType
for i in 1..len(ptype)-1:
if ptype[i].kind != nnkSym:
error("closure: all argument types of procedure must be named")
var fn = newTree(nnkLambda,
newEmptyNode(),
newEmptyNode(),
newEmptyNode(),
newNimNode(nnkFormalParams),
newEmptyNode(),
newEmptyNode(),
newStmtList(newCall(pname)))
var args = fn[3]
var call = fn[6][0]
let rtype = ptype[1].typeKind
if rtype == ntyEmpty:
args.add newEmptyNode()
else:
args.add ptype[1]
for i in 2..len(ptype)-1:
let sym = genSym(nskParam, "t" & $(i-1))
args.add newTree(nnkIdentDefs,
sym, ptype[i], newEmptyNode())
call.add sym
return fn
when isMainModule:
import strutils
let p: proc(x: string): int = closure parseInt
echo p("007")
Is there any problem with automating this conversion in the compiler that I am not seeing?
Edit: To be clear, if it's just "doesn't fit in the pipeline before 1.0" that would be a perfectly understandable answer.