> Separate compilation is a form of abstraction (at least), because when the 
> app gets linked against the library/dll, types are gone and an ABI only 
> remains.

sure, and nim supports doing it too. Just have one module that declares 
procedures with `{.importc.}` and another that defines them with `{.exportc.}` 
and don't compile them together, this is exactly what C++ is doing, except C++ 
forms the symbol name in a different way.

It's good that nim doesn't force you to create an ABI too, since that way your 
ABIs will be more intentional.

Actually if you write code like this then nim supports exactly the same kind of 
separate compilation as C++ and C, it's not not a good idea.

> Your example defines (implictly) a supertype that defines a field a : int , 
> and a derived type Notvisible that is not known at main.nim. However, the 
> supertype is known at main.nim, because it has access to the field a : int .

Yes that's the point. The supertype is known (reachable) but not visible. You 
can't write out an identifier that refers to the supertype by name. This is 
_[much](https://forum.nim-lang.org/postActivity.xml#much) much better than 
making everything reachable also visible.

If everything reachable is also visible then changing the set of imported 
modules is always a breaking change for any "downstream" modules since they can 
all name things from those modules without actually importing them. It also 
greatly increases the risk of name collision.

> Interesting. I'd say that Nim creates dependencies between types that can't 
> be seen easily. Notvisible and its field a : int do have the "*" export 
> qualifier both. But they are used differently and there is an abstraction 
> barrier between them.

Yes, there is an abstraction barrier between them, and that's what modules let 
you write. Without modules Notvisible would be usable in the same ways as 
Visible and would have to be if you ever wanted to use Visible. There's a 
dependency between the types no matter what, since the compiler needs to know 
NotVisible's size.

> Every language that allows for types in macro-expandable code allows to hide 
> these parameters behind a common superclass (or function) where the 
> instantiated param. type doesn't occur. In C++ it is done with virtual base 
> class functions and the derived class functions are implemented with an 
> instantiated generic component. You will find the same design pattern in 
> Java, C#, Rust, Swift, Kotlin, etc etc. And now Golang is just on the corner 
> to do the same.

Nim allows this too use object types and a table of function pointers, or any 
other method of type erasure. Golang can also do type erasure through their 
interfaces (that's the point of interfaces). Almost no languages make the 
distinction between a concrete indirect type and generic expansion automatic, 
Ada might, but I'm not sure.

> What effect do have concepts in Nim? In C++, templates can have effects (they 
> are turing complete) and therefore, concepts will have effects too because 
> they impose restrictions on templates.

generics alone are probably turing complete in nim, but it's not something 
anyone bothers to exploit because they can just write actual nim code at 
compile time. Being turing complete does not mean your code has externally 
visible effects. I'm not really sure what this means at all. In nim concepts 
are just like c++ concepts, they constrain generics and the constraint is 
checked before instantiating the generic. Indeed adding a constraint can cause 
a generic that was previously used for a function call to no longer appear in 
the final overload set.

> In Nim : What does a construct = concept t, type T really mean? (the T part) 
> Is it possible to bind an intrinsic type (existential type) to T ? I tried 
> several things but I couldn't succeed.

it looks like `type` on the right hand of the concept I think that:
    
    
    type AdditiveMonoid = concept x, y, type T
        x + y is T
        T.zero is T
    
    
    Run

is about the same as c++: 
    
    
    ++
    template<typename T>
    concept AdditiveMonoid = requires(T x, T y) {
        { x + y } -> std::same_as<T>;
        { T.zero } -> std::same_as<T>;
    };
    
    
    Run

I don't really know what existential types are, I'm not familiar with 
functional programming. You could add more parameters to allow some other types 
to possibly add to get a T, but you'd have to specify what types you're talking 
about when you use the concept.

> We don't have vtables in Nim and therefore, if we want abstraction, we have 
> to resort to module abstraction. If not, we have to deal with a declared type 
> system and an (undeclared) implicit type system as it is the case just now. 
> For a small project, it doesn't play a role, it simply works - obviously. But 
> I doubt that this will hold for projects on a larger scale.

Modules and vtables don't solve the same problems at all, and besides you can 
write vtables in nim if you want! objects have the correct casting rules, and 
you can even use dot operator overloading to dispatch calls through to vtable 
functions. Nim just doesn't pick a layout for you. (although nim does have 
methods, and you can use them similarly to vtables in c++. You can even use a 
macro to pull in the correct procedures when you're creating the vtable data 
for a particular class type.

Reply via email to