It works, but I'm not sure if Nim supposed to be used that way?
I declared a generic proc run that can handle any type that has id field and process proc defined. So J generic type acts like an interface, is it ok to use it that way without explicitly specifying those requirements for the J type?
# Generic function that expect that there should be
# declared `id` field and `process` function for J type
proc run*[J](job: J): void =
let id: string = job.id
let v: string = job.process()
echo "id=", id, " v", "=", v
type SomeJob = object
id: string
proc process(job: SomeJob): string = "some value"
SomeJob(id: "1").run()
StalableTask in Weave: https://github.com/mratsim/weave/blob/2e528bd2/weave/datatypes/prell_deques.nim#L10-L20
type
StealableTask* = concept task, var mutTask, type T
## task is a ptr object and has a next/prev field
## for intrusive doubly-linked list based deque
task is ptr
task.prev is T
task.next is T
# A task has a parent field
task.parent is T
# task has a "fn" field with the proc to run
task.fn is proc (param: pointer) {.nimcall.}
Explanation: It's a type that allows intrusive linked lists, has a parent field and has a callable proc in a field
TrainableLayer in Arraymancer: https://github.com/mratsim/Arraymancer/blob/88edbb67/src/arraymancer/nn_dsl/dsl_types.nim#L63-L68
TrainableLayer*[TT] = concept layer
block:
var trainable: false
for field in fields(layer):
trainable = trainable or (field is Variable[TT])
trainable
Explanation: it's a type that contains a Variable type at any nesting level.
Quadratic and Cubic extensions in Constantine: https://github.com/mratsim/constantine/blob/28e83e7b/constantine/tower_field_extensions/tower_common.nim#L26-L39
type
CubicExt* = concept x
## Cubic Extension field concept
type BaseField = auto
x.c0 is BaseField
x.c1 is BaseField
x.c2 is BaseField
QuadraticExt* = concept x
## Quadratic Extension field concept
not(x is CubicExt)
type BaseField = auto
x.c0 is BaseField
x.c1 is BaseField
Those are types which contain the same inner type twice (Quadratic extension) or thrice (Cubic Extension). For example
type Complex[T] = object
c0, c1: T
would fit quadratic concept. In my case T is a modular big integer or a Complex or a Complex of Complex ...
Also, interesting thing is that job.process() compiles but process(job) does not.
You can solve this by adding "mixin process;" inside the generic proc, see https://nim-lang.org/docs/manual.html#generics-symbol-lookup-in-generics - you have to keep it an "open" symbol.
Indeed, interesting and very confusing -- even though run() is only instantiated at the end, it doesn't have access to process because it is a "closed" symbol.
I added a
process[J](job: J): string = ""
in the beginning, which does compile and run, but uses the generic process(job: SomeJob)
Running
# Generic function that expect that there should be
# declared `id` field and `process` function for J type
proc process[J](job: J): string = "generic value"
proc run*[J](job: J): void =
let id: string = job.id # Expecting id field
let v: string = process(job) # Expecting process proc
echo "id=", id, " v", "=", v
type SomeJob = object
id: string
proc process(job: SomeJob): string = "some value"
run(SomeJob(id: "1"))
echo process(SomeJob(id: "1"))
Produces this output:
id=1 v=generic value
some value
I would have been happy with a forward generic proc, but these are not accepted by the compiler (there is no mention in https://nim-lang.org/docs/tut1.html#procedures-forward-declarations whether it should or shouldn't)
So in this case, we have two "proc process(J:SomeJob):string" in the same file/scope, and both get included and executed, and the question about which one gets executed depends on the order of the definition between the generics and the instantiation. Am I misunderstanding something? I would expect at least a warning, if not an error - and also the ability to forward declare a generic proc, perhaps - though an error/warning in this case would be much more helpful.
Experiment in https://play.nim-lang.org/#ix=2xiY
Bump.
Is this expected behaviour: two proc process(job: SomeJob) in the same scope/context without error, although only one accessible at any point in the source? My expected behaviour would be an error, either at compile time or at the latest at link time (on an AOT backend), but I might be misunderstanding something.
See previous message for example.
this is a huge feature of generic dispatch, where multiple typeclasses might match a type for a generic function, the compiler will choose the narrowest fit, or let you know at compile time that the choice is ambiguous.
e.g. there's a default proc '$'[T:object](x:T):string and you can still overload it for your own object types proc '$'(x:SomeJob):string = "someJob"
I understand the general use case, but I think this case is very confusing and should be flagged. Simplified version: (play in : https://play.nim-lang.org/#ix=2yIv )
type SomeJob = object
id: string
# one order
proc process1[J](job: J): string = "generic value"
echo process1(SomeJob(id: "1"))
proc process1(job: SomeJob): string = "some value"
echo process1(SomeJob(id: "1"))
# reordered
proc process2[J](job: J): string = "generic value"
proc process2(job: SomeJob): string = "some value"
echo process2(SomeJob(id: "1"))
echo process2(SomeJob(id: "1"))
The output produced is:
generic value
some value
some value
some value
Moving the "echo" past the 2nd proc definition changes the output. I would expect at least a warning in this case, because even though the ambiguous match IS resolved to the more specific version, as one would expect, both are instantiated and used in the same scope, and which one exactly is used in each case depends on things like macro expansions etc, which are not self evident.
I would expect reordering of lines to make it compile/not compile because of a future reference, but not the output to change.
Furthermore, with the planned code-reordering that would remove the need for forward declarations, the meaning of the 1st order in the example may change on reordering.
Yes, I did realize I hadn't read your previous post properly, and I missed your point entirely. To continue to miss your point for a second, may I point out that either {. experimental: codeReordering.}, or adding mixin process to run both produce the desired result and allow removing process[T] entirely.
With {. experimental:"codeReordering".} in your process1 example, both versions do indeed output the same, and I look forward to that becoming standard, forward declarations and hard to debug inconsistencies shouldn't be part of a modern language.
Code reordering wouldn't change the (imho more common) case where overloading happens across file boundaries
#run.nim
proc process*[T](job:T):string="default"
proc run*[T](x:T) = echo x.process
#usercode.nim
import process
type
SomeJob = object
proc process(job:SomeJob):string = "some job"
let j = SomeJob()
j.run
echo j.process
Unless process was specifically intended to be overloaded within run, and so annotated with mixin, the sane behaviour is to dispatch to process[T], I do not think users overloaded procs should affect library/module code, or cause a warning/error in these unambiguous cases.
So I agree, unexpected, but I'd like to see codeReordering to make the output consistent rather than the compiler searching through all instantiations to see if a different choice was made at some point.