I should also ask, in the nim tutorial part 2, it mentions that composition is better than inheritance, and I'm confused about how to type based on composition when using objects. I know you can use composition with tuples like so:
type
Test = tuple
x, y: int
AnotherTest = tuple
x, y: int
var x: Test = (x: 10, y: 11)
var y: AnotherTest = (1, 100)
var z: Test
z = x
x = y
y = z
I should also ask, in the nim tutorial part 2, it mentions that composition is better than inheritance,
I think your example above is not related to composition.
Composition is just packaging small objects into larger objects. The GoLang people are talking often about composition over inheritance, but I think GoLang has special support for it. (In Nim, when we construct a Circle by putting a center coordinate or a RGBA color in it we may get ugly long names like circ.center.x or circ.color.g, I think golang can avoid that.) When we use inheritance we have to use references, and we have generally one parent. References can be bad for optimal performance because of indirection and bad cache usage, and one parent can be inflexible. In Nim composition generates no indirection for value objects, while for languages with no value objects like Ruby it would generate indirection. I can not remember if I wrote something about this topic in the Nim for Kids book, maybe I should.
Yea, Nim doesnt have much in it to automate the compositional method for you, but there are libraries that add it. We also have concepts which allow you to coerce multiple types into a generic based off their implementations.
type Addable[T] = concept c
c + c is T
proc add(a, b: Addable): Addable = a + b
echo 10.add(11)
echo 1321f.add(321312f)
To answer your question: use objects. If you really have to, use tuples.
And take a look at converters in case you need a short syntax for object construction, example
You are absolutely right, converter creates additional function call.
I also run a simple benchmark:
import benchy
type
Rectangle = object
x, y, width, height: float
converter tupleToRectangle*(self: tuple[x, y, width, height: float]): Rectangle =
Rectangle(x: self.x, y: self.y, width: self.width, height: self.height)
timeIt "tuple creation":
for _ in 1..10_000_000:
var rect1: Rectangle = (x: 400.0, y: 280.0, width: 40.0, height: 40.0)
keep(rect1)
timeIt "object creation":
for _ in 1..10_000_000:
var rect2 = Rectangle(x: 400.0, y: 280.0, width: 40.0, height: 40.0)
keep(rect2)
and results are same:
name ............................... min time avg time std dv runs
tuple creation ..................... 2.330 ms 2.345 ms ±0.018 x1000
object creation .................... 2.330 ms 2.343 ms ±0.012 x1000
So it seems that C compiler is pretty good at its job, we can happily use converters left and right!converters in your codebase
Well, not all people may have heard about problems of converters reported in the last 5 years. I can remember some reports about increased compile times, and strange, hard to find bugs. One of the reporters was mratsim, so I took the reports serious and avoided converters for all my code including gintro.
Pretty much yeah, shorter syntax
Rectangle(x: 5.0, y: 5.0, width: 10.0, height: 10.0)
vs
(5.0, 5.0, 10.0, 10.0)
I find it convenient for common geometric shapes and color which have a pretty much defined order of parameters, basically: Vector2,3,4, Rectangle and RGBA color
avoided converters for all my code including gintro.
I understand your reasoning but I think same could apply to anything else like generics: bugs with generics? don't use them. Which is not good in the long run as long as we still have converters
Well, not all people may have heard about problems of converters reported in the last 5 years. I can remember some reports about increased compile times, and strange, hard to find bugs. One of the reporters was mratsim, so I took the reports serious and avoided converters for all my code including gintro.
I think I reported it on the forum and/or IRC since it's not a Nim bug per se but here is my main issue: https://github.com/mratsim/Arraymancer/issues/394 and fix https://github.com/mratsim/Arraymancer/pull/397.
What happened was that those converters which were supposed to be internal to Arraymancer to ease development (https://github.com/mratsim/Arraymancer/pull/350/files#diff-8ab735417f4c57143f9df0fd7defd40f1ba4f45a6ac34e8dda2f31c330f13863R7-R13) leaked out to library users preventing even $ from working: https://github.com/mratsim/Arraymancer/issues/394 due to ambiguous calls system.$(x: uint64) and complex.$(z: Complex)
Furthermore converters significantly increase compile-times. And they also made debugging abs issues (absolute value) hell because I didn't realize I was calling complex abs due to converters.
So as a guideline:
I had numbers to complex converters in Arraymancer.
if you expose converters in a library, do so in an optional module called lenient_something so that importing converters is always opt-in as it's hard to opt out of them
I like this approach, thanks!