Hi,
I wanted to encode entities with an "intrinsic" (existential) type and a thing that comes close to it is described in :
https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
I had to extend the pattern a bit with a template taking two types, the latter for the intrinsic type and the implementation requires three steps : a constructor pattern, the definition of constructor classes (being templates themselves) relying on the constructor pattern, and finally the instantiations of types (classes) that contain the intrinsic type.
I then can "at will" decide if I want to keep the instantiation type abstract because it can never be seen from the outside ("we don't now"), or I can "pull it out" with type reconstruction, in particular using a template<template> construct. The latter can be regarded as a rank-2 type, because the type is not given by forehand.
At my surprise, it is possible within C++. It shows that C++ retains the abstract part of a template pretty well - at least for the purpose. If C++ allowed for better abstractions, we could do more with it. But C++'s templates were introduced at a time where these extensions could not be seen - they were not understood yet. (Instantiation types can't reflect abstract types in a satisfying way...)
However, the implementation is entirely static. Where in other languages, it would be done with virtual functions with the help of a runtime table.
#include <stdio.h>
#include <iostream>
using namespace std;
#define reif (static_cast<T&>(*this))
//constructor pattern
template<class T, typename V = int>
struct pcset {
void show() { reif.mshow(); }
void set() { reif.mset(); }
void sumop(T& v){ reif.msumop(v); }
void notimpl() { reif.mnotimpl(); }
void mshow() { printf("dummy: %d\n" , dummy); }
void msset() { printf("set not implemented yet!\n"); }
void msumop(T& v) { printf("sumop not implemented yet!\n"); }
void mnotimpl() { printf("... not implemented yet!\n"); }
int dummy; protected:
V mc;
static V static_op( V a, V b) { return T::mstatic_op(a,b); }
static V mstatic_op( V a, V b) {
printf("static_op not implemented yet!\n"); return a; }
} ;
//constructors
template<class A, typename U, typename V>
struct dvd_ggen : pcset<A,V> {
U ma,mb;
void init(U a, U b) { ma = a; mb = b; this->mc = b; this->dummy = 0; }
void mshow() { cout << ma << " " << mb << " " << this->dummy <<"\n" ; }
void mset() { ma = mb; }
void msumop(A& v) { ma = this->static_op(ma,v.mb) ; }
static V mstatic_op(V a, V b) { return a + b ; }
} ;
template<typename U>
struct dvd_gen : dvd_ggen<dvd_gen<U>,U,U> {};
struct dvd_int : dvd_ggen<dvd_int,int,int> {
void init (int a,int b) { this->dummy = b; }
static int mstatic_op(int a, int b) { printf("I'll do nothing for you!\n"); return 0 ; }
void mnotimpl() { printf("surprise! surprise!\n");}
} ;
//instantiations: bind the intrinsic type
dvd_gen<int> da,db;
dvd_gen<string> dc,dd;
dvd_int de;
//access with template<template> a rank-2 quantifier
template <template<typename> class A, typename T>
void binop(A<T>& b, A<T>& c) {
b.sumop(c) ; b.show() ;
T temp = b.mb; b.ma = temp;
}
//some operations
int main(int argc, char *argv[]) {
da.init(5,7) ; da.show();
db.init(1,2); db.sumop(da); db.show();
dc.init("alpha","beta"); dd.init("delta","epsilon");
dc.sumop(dd); dc.show();
de.init(0,42) ; de.set() ; printf("...show\n") ; de.show();
dd.notimpl();
de.notimpl();
//binop<dvd_gen<int>>(da);
binop(dc,dd);
}
My question is: How could this be achieved in Nim? Or, alternate question : How abstract are instantiation types in Nim?
I tried here https://github.com/mratsim/weave/blob/a2e118b/experiments/e01_staccato.nim#L17-L42
Unfortunately it crashes the compiler:
type FibTask = object of Task[FibTask]
n: int
sum: ptr int64
## This crashes the compiler
## with `Error: internal error: getTypeDescAux(tyOr)`
##
# var x: FibTask
Are we sure this is still the case, I did fix some stuff with recursive generics which released with 1.6, the follow does compile.
type
Task[T] = object of RootObj
data: ref T
doThingInternal: proc(a: T) {.nimcall.}
FibTask = object of Task[FibTask]
n: int
sum: ptr int64
proc doThing[T](a: Task[T]) = a.doThingInternal(a.data[])
var a: FibTask
a.doThingInternal = proc(fib: FibTask) = echo fib.n
a.data = new FibTask
a.data.n = 300
a.doThing()