I have a couple of modules : mdlimpl, mdlabst and mdlprog. mdlimpl implements (you guess it) some basic functionality. mdlabst (abstraction) imports mdlimpl and operates on a type TOb given by mdlimpl, but the type gets not revealed in mdlabst. To achieve this, functions defined by mdlimpl only are used. Last not least, mdlprog imports mdlabst but not mdlimpl.
#mdlimpl
type
TOb* = object
a*, b* : int
proc oinit*(x : int) : TOb = TOb(a:x,b:0)
proc oopn*(obj : TOb , x : int) : TOb = TOb(a:obj.a, b: obj.a+x)
proc oprint*(obj : TOb) = echo obj
#mdlabst
import mdlimpl
type
Tcon* = concept x,type T
oinit(int) is T
oopn(x,int) is T
oprint(x)
proc reif(x:Tcon) = oprint(x)
proc con*(x :int) : Tcon =
result = oinit(x)
result = oopn(result,2)
reif result
#mdlprog
import mdlabst
type
Tcr = concept x, type T
x.b is int
proc reif(x:Tcr) =
echo x
proc run() =
var v : Tcon = con(5)
v.b = v.b + 5
reif v
run()
Unfortunately, run() does not have access to any of the abstract (constructor, operator) functions provided by mdlimpl. This is bizarre because Tcon , provided and exported by mdlabst , just checked that they are available!
The job of Tcon is to check whether TOb supports a certain API, not to make that API accessible:
#mdlimpl
type
TOb* = object
a : int
b : int
proc oinit*(x : int) : TOb = TOb(a:x,b:0)
proc oopn*(obj : TOb , x : int) : TOb = TOb(a:obj.a, b: obj.a+x)
proc oprint*(obj : TOb) = echo obj
#mdlabst
import mdlimpl
export mdlimpl.oinit, mdlimpl.oopn, mdlimpl.oprint
type
Tcon* = concept x,type T
oinit(int) is T
oopn(x,int) is T
oprint(x)
proc reif(x:Tcon) = oprint(x)
proc con*(x :int) : Tcon =
result = oinit(x)
result = oopn(result,2)
reif result
#mdlprog
import mdlabst
proc run() =
var v : Tcon = con(5)
v = oopn(v, 5) # should probably have been `v.b = v.a + 5` in the original code
oprint(v)
run()
Is that the separation you wanted?
Is that the separation you wanted?
You just tried to achieve separation with inclusion, look at your imports. But additional boilerplate code does not address the problem:
# should probably have been v.b = v.a + 5 in the original code
You got the point and missed it at the same time.
I intentionally wrote : v.b = v.b + 5
because it is the wrong translation of the original assignment given in oopnv(T,int) where T stands for the intrinsic type, being an existential Type. It should better be written xT for that purpose.
If I now rewrite mdlprog:
proc oopn(obj : TCon , x : int) : TCon =
result = obj
result.b = result.b + x
proc run() =
var v : Tcon = con(5)
v = oopn(v, 5)
oprint(v)
it would look that I can do the same thing in mdlprog as in mdlimpl, it looks similar, but it isn't.
At the same time, I had redefined the underlying type, because I changed the operations that can be run over it.
So I gave evidence for:
That is possible because I could "open" (reify) the underlying nominal type of the existential Type xT , given in mdlabst, at the top level mdlprog and build my own function on it. Therefore, I can make a B of a A or an X for an U.
But logically, this is not derivable, neither in classic logic nor intuistic logic.
What then? Well, let's call it "Nim logic" within recent Nim.
This might change in the future. Or, it is a generational problem.
The reason for #1 is probably that the Nim compiler doesn't evaluate generics like concepts into actual type classes, but just uses them as predicate filters for types. For a matching type like TOb, the definition of Tcon effectively mutates into type Tcon* = TOb. The "pragmatic workaround" would probably be "Don't export the fields, then.", which works until there is a fourth module which actually needs access to them...
As to #2: imagine #1 was solved, if there was a macro makeApi(Tcon) which generates exported delegate procs for the ones defined in mdlImpl (instead of the re-exports in my code), would that meet your criteria?
The "pragmatic workaround" would probably be "Don't export the fields, then.", which works until there is a fourth module which actually needs access to them...
This will simply not happen because types being abstract remain abstract, they can't be opened with the underlying type or fields outside the defining module.
As to #2: imagine #1 was solved, if there was a macro makeApi(Tcon) which generates exported delegate procs for the ones defined in mdlImpl (instead of the re-exports in my code), would that meet your criteria?
Macros would not help here, they have a peep hole look on the AST only . If they are intended work, the would need access to the entire environment.
This is the main point with concepts: Concepts are orthogonal to the visibility aka "module" system in Nim , they have to overcome the limitations of these visibility rules. So, a concept should be able to find out that a specified proc func can be found somewhere in the module tree and the implementer/user should not be enforced to do that for the compiler .
Then, but only then, concepts would become convenient to use.
There are cheaper solutions possible though: Here : https://forum.nim-lang.org/t/8219 (export markers for procs) and here https://forum.nim-lang.org/t/7925 (compiler building its own concept for a module, detecting "prototype" behaviour).
Macros would not help here, they have a strictly local look on the AST only . But they had to inspect different modules, they had to access the entire environment.
Well, there is macrocache which offers non-local AST storage, could help to at least simulate these - how do we call them, "static traits"?