First of all, last time i talked to fowl he told me he wasn't working on it, but still i read from time to time he is. Maybe he changed his mind, but if not, we are misleading people.
Second, e2718e as you already have discovered, nim is all about features that only work on compile time, maybe because they are more performant. Concepts are very close to interfaces, but for example, you cannot have a seq or array of concepts.
But still, it is a very usefull feature for interfacing. This pathfinding library is a good example on how to use them https://github.com/Nycto/AStarNim
@e2718e: Are you aware that you can do many things in Nim without interfaces, where other languages require to specify an interface? For instance you can do things like this:
proc energy[PhysicalObject](o: PhysicalObject): float =
0.5 * o.mass * o.speed * o.speed
type
SimpleObject = object
mass: float
speed: float
type
ComposedObject = object
proc mass(co: ComposedObject): float =
# fancy calculation...
42
proc speed(co: ComposedObject): float =
# fancy calculation...
42
let simple = SimpleObject(mass: 1.0, speed: 5.0)
echo simple.energy
let composed = ComposedObject()
echo composed.energy
So there is no need to explicitly write which interface/super-class PhysicalObject has to implement. As long as the compiler has everything that is required, it just works. Kind of like a static duck typing. I like this a lot, because for instance in Scala writing type annotations can be annoying when a class is supposed to implement many traits/interfaces. If you want to make it more explicit what functionality has to be provided, use a concept.
@rku: To my knowledge, in Java, Scala, C#, and Rust you cannot write generics in this way. If you want to do anything with the type which goes beyond functionality of the fundamental base type, you have to specify the type constraint explicitly. Which is why you also have to introduce a corresponding interface, trait, or base class, and inheritance plays a much more important role in general. For me, it was a bit of a surprise to have these unconstrained generics. I was bringing it up because I didn't know whether e2718e has the same background. Basically when learning Nim, I went from "how on earth should I write code without interfaces/traits" to "no interfaces works just fine".
Could you enlighten me what you mean by "it does not solve problems that interfaces solve"? From my experience of porting Scala to Nim I simply have solved many problems by generics where traits were required in Scala. I haven't spend much thought much about the limitations so far.
First, there are interfaces in the Java/C# tradition. They basically provide a very limited form of behavioral inheritance and are primarily used to provide a limited form of multiple inheritance and type bounds for generics.
Using interfaces as type bounds for generics creates problems (for example, with constructors). Nim uses structural typing by default for generics and and has concepts, which are generally more powerful than Java interfaces for specifying type bounds.
Multiple inheritance of interfaces is something that Nim has less need for. The reason is that methods are not tied to a class, but can be added in a post-hoc fashion. Extending the interface of an object can be done by importing a module defining additional methods. The one limitation is that you may be sacrificing type safety, since there has to be a common supertype; on the other hand, you basically get dynamic typing for free if you always inherit from RootObj or RootRef. In any event, the solution here would be to add multiple inheritance, either of stateful objects (tricky) or stateless objects (relatively straightforward [1]).
Second, you have Go interfaces. The actual language feature here is not really interfaces, but structural subtyping: I.e. you don't have to specify an explicit inheritance relationship as long as the type signatures match. This is basically done to address the following problems:
As far as Nim is concerned, each use case can be addressed differently:
[1] Other than having to resolve the ambiguity between multiple implementations of the same method in supertypes.
[2] With the caveat that Nim currently may require unnecessary heap allocations, because object types have some limitations on polymorphism that ref object types do not have.
My goal for the vaguely defined "interface" is unit-test independence.
Because Nim compiles so fast, this does not seem to be an issue for small numbers of developers on small projects. But at larger scale, I cannot over-emphasize the importance of fast, independent testing.
Suppose I author module X, which uses method foo(:YA) of module Y, which in turn uses bar(:YA) of module Y. In other words, X does this:
# In module X
import Y
proc top*(y: YA) =
y.foo()
And YA also needs bar() defined, because foo() will use it:
# In module Y
proc foo*(y: YA) =
y.bar()
proc bar(y: YA) =
echo "snafu"
I want to test my module X without building Y. Later, someone will write program P, built with both X and Y. main() in P will call top(). The author/builder of P must somehow create a YA object and inject it.
import X, Y
proc main() =
var y: Y.YA = Y.createYA()
X.top(y)
In Go, my module X can include a test which provides a fake or mock version of foo(YA). top() itself would accept an interface which includes foo().
In Nim, one solution is to include a file Y.nim in my tests for X. Essentially, I would inject an entire module into the build, but with only a subset of functionality, strictly for the sake of testing module X. If that's the recommended approach, fine, but different tests might need different mocks for foo().
Instead, top[YA](y: YA) could be generic, which @bluenote called "unconstrained generics":
# In module X, no need to import Y
proc top*[YA](y: YA) =
y.foo()
(I suppose I could even use a concept to make the requirement of foo() more transparent, but note that the concept must be declared in X, not in Y. Can X and Y have different, matching concepts?)
For me, it was a bit of a surprise to have these unconstrained generics. I was bringing it up because I didn't know whether e2718e has the same background. Basically when learning Nim, I went from "how on earth should I write code without interfaces/traits" to "no interfaces works just fine".
If this really is the Zen of Nim, maybe this feature needs to be highlighted better for novices.