Hello all,
I can't seem to find an answer to this one, and I'm a little stumped. So I'm reaching out to get a better understanding as to what is happening here. So I'm building a small project which relies on OOP in Nim, and specifically the inheritance aspect.
So take for example, I have the following classes/interfaces/what-have-you:
type
Application = ref object of RootObj
resources*: seq[Resource]
type
Resource = ref object of RootObj
things*: seq[string]
method consume*(self: Resource) {.base.} = echo("Consumed!")
Let's say the Resource parent has multiple base methods with default implementations that can be overridden. And say I have the following class/interface/what-have-you that inherits from Resource.
type
FoodResource = ref object of Resource
method consume*(self: FoodResource) = echo("Eaten!")
So here is the start of the weird part that I don't understand. When I instantiate an Application, and assign an instance of FoodResource to its property resources, it will throw a type error... which I guess is fair, given that FoodResource really isn't type Resource.
let food = FoodResource(
things: @["Apple", "Orange", "Pear"]
)
let app = Application(
resources: @[food]
)
But when I create another class/interface/what-have-you that inherits from Resource and assign an instance of that together with the instance of FoodResource to resources property of the instance of Application, it works.
type
FuelResource = ref object of Resource
method consume*(self: FuelResource) = echo("Guzzled!")
let food = FoodResource(
things: @["Apple", "Orange", "Pear"]
)
let fuel = FuelResource(
things: @["Shell", "BP"]
)
let app = Application(
resources: @[food, fuel]
)
So my question is, how does the shorthand initialisation of a sequence infer it's internal types when mixed with classes/interfaces/what-have-you that inherit from a parent? Why does it enforce the parent type Resource when there is only one item in the sequence but will allow for inherited types when there is multiple items in the sequence? I do hope this all makes sense.
You can explicitly convert the FoodResource to a Resource:
var app = Application(
resources: @[food.Resource]
)
I believe the reason is:
@[food] is a seq[FoodResource], which can not be converted to a seq[Resource], since seq is an invariant type.
@[food, fuel] is a seq[Resource], since there's no other option.
That actually makes sense. I just also worked out (just before reading your reply) that explicitly converting the first element to the Resource type:
var app = Application(
resources: @[(Resource)food]
)
Thanks for the help! It actually totally makes sense now!
Please don't convert types that way. Here are three options:
Resource(food)
food.Resource
Resource food
To put it another way, borrowing a geneaological term:
when initializing a seq with a set of inheritable types, the resulting type will be that of the most recent common ancestor of those types.
type
Fish{.inheritable.} = ref object
RayFinnedFish = ref object of Fish
Tuna = ref object of RayFinnedFish
LobeFinnedFish = ref object of Fish
Coelacanth = ref object of LobeFinnedFish
Tetrapod = ref object of LobeFinnedFish
Human = ref object of TetraPod
Chicken = ref object of TetraPod
let adam = Human()
let eve = Human()
let fifi = Chicken()
let jack = Tuna()
assert typeof(@[adam,eve]) is seq[Human]
assert typeof(@[adam,fifi]) is seq[Tetrapod]
assert typeof(@[eve,adam,jack]) is seq[Fish]
These are the three ways to call a function in Nim. Using redundant parentheses is anti-idiomatic. It's like writing a hello world program as
(echo)"Hello world!"
(typ)variable looks like a C cast, but it's only an expression in braces which evaluates to a function in command syntax (once the braces evaluate it's equivalent to typ variable). This matters because the proc call has a different priority compared to C casts. Compare for example cases like (uint64_t)variable * 2 and (uint64)variable*2. So I think this form should be avoided because it can lead to errors.
Castings can be called in Nim just like any other proc can be called. Like @ynfle suggested this means that a C++ style casts are supported as well (typ(variable)), which is how I usually do it, though it's a purely stylistically question, many have suggested dot calls in this thread.
@SolitudeSF Is this meant to be a helpful comment?
@ynfle int64(n) implies a conversion to me, this makes sense. I will keep this in mind going forward.
@doofenstein Your explanation also makes sense, will keep this in mind. I can see where the C style casting syntax could lead to errors.
As someone who is trying out Nim, my understanding of Nim's syntax is that it is incredibly flexible given that in this thread, many have already pointed out multiple ways to convert a type (and for that matter call a procedure/method/function), it wouldn't be far fetched for someone like me to assume that the syntax is flexible enough to be conform to existing habits of similar languages (or underlying languages that Nim compiles down to). The fact that Nim in itself borrows many of it's features and syntax styles from various languages, I don't see why having this assumption is so objectionable.
I have already said that I will keep this in mind going forward, and various users have already pointed out to me a technical reason as to why it would be bad practice, and I accept that. I'd like to point out though, for a growing community like Nim's, it is in my opinion that some unhelpful comments within this thread don't help adoption by new users.
I would like to point out too that Nim distinguises between type casts and type conversions.
In the words of the Nim manual > Casts are done with the cast operator and force the compiler to interpret a bit pattern to be of another type > [Type conversions] preserve the abstract value, not necessarily the bit-pattern
The distinction is important to remember, as a type cast in C is more similar to the Nim cast operator, while a type conversion in Nim is more of an annotation for the compiler to determine if the type conversion relationship is even possible and is useful for OOP.
Such assumption and expectation makes no sense.
Come on, give @TokenChingy a break. Reasoning by analogy happens to the best of us.