V: Extending builtins and stdlib structs & interfaces

Created on 18 Sep 2019  路  14Comments  路  Source: vlang/v

How does one extend a builtin or stdlib struct with additional members? I mean without wrapping the existing struct into a new one as it complicates things in practice.

Can one actually wrap an existing interface with a new interface? Or somehow embed interface into interface? Or combine them?

Feature Request Discussion

Most helpful comment

What does for mean in this context?
That s3 has the fields of both s1 and s2?
Would not mixes or extends be a better keyword for that?

All 14 comments

It's not possible to add fields to structs from other modules.

It's not possible to add fields to structs from other modules.

What would be the recommendation of dealing with this issue? This is largely a question of significant code bases which need this as the other option is to reimplement major parts of the standard library & intrinsics APIs (mostly wrapping them) just to get the ability to extend them. This goes fully against the V philosophy :cry:. Any ideas?

In the worst case some builtin support to ease this "reimplementation", something like the current addition of a new method for a struct, but for addition of a new struct field?

If not structs for now, then how about interfaces and their extension in other modules?

I can't think of any language that allows modifying structs, only extending types via extension methods, like C#.

For that you can simply use functions in V.

I can't think of any language that allows modifying structs

Maybe not modify structs per se, but modify a "compound structure with methods" (usually called a class in classed-based languages, which is pretty much any language nowadays). In most languages nowadays one can make a compound structure, then a either additionally modify it (in Python, Ruby, and all dynamic languages) or make a derived compound structure e.g. by inheritance or by using prototype-based OOP or whatever way you can think of.

I personally like even simpler concept - so called "abstract interfaces" and "concrete interfaces" while allowing mixing (yeah, similarity to "mixins" from OOP) of abstract interfaces together to form a concrete interface. You can imagine abstract interfaces as structs being a plain interface with fields and methods, but without values in fields and without implementations of the methods (but you can have default values for these fields and default implementations, but they're not stored there so to say, even though syntactically they're e.g. part of the struct declaration).

You can then instantiate an abstract interface (or more of them) by defining all methods (if no defaults) and by assigning to all fields (if no defaults) to produce always one instance in (contiguous) memory (disregarding how many abstract interfaces were "mixed in"). This instantiation will create so called "concrete interface". The nice thing is, that such instance is compatible with all the abstract interfaces and you have no more the issue with extending of builtins etc. This actually allows for even smaller standard library btw. (there is no more the need to have that many builtin methods and deciding which are still useful for most users and which are not).

These "abstract" and "concrete" interfaces are implemented e.g. in Dao, but in Dao, they're interconnected with the highly capable OOP type system, which is not necessary in V as the concept of "abstract" and "concrete" interfaces is universal and independent from type system and doesn't require any OOP whatsoever.

Actually syntactically everything in V could stay as it is now, but semantically all types would become interfaces also and then there would be just the new syntax for "mixing" them together while instantiating them - in Dao this mixing is done using the for keyword when defining the type (I could imagine something like:

struct s1 { f1 int }
struct s2 { f2 str }
struct s3 for s1, s2 { f1: 5 f3 int }
x := s3 { f2: 'abc' f3: 9 }  # in case of some unimplemented methods on s1 or s2, the compiler would yell
println( x )

What does for mean in this context?
That s3 has the fields of both s1 and s2?
Would not mixes or extends be a better keyword for that?

What happens if s1 and s2 both have a field with the same name?

@dumblob lots of new languages are not OOP (e.g. Rust and Go).

V is going to keep it simple, just like Go: it'll have interfaces (Rust's traits) and embedding. That's it.

Would not mixes or extends be a better keyword for that?

Yeah, I just "copy pasted" how it's done in Dao (one of the goals of Dao was to keep number of keywords quite minimal). mixes or extends makes more sense, of course.

What happens if s1 and s2 both have a field with the same name?

Compile time error at the place of field's use unless used with a canonical name (canonical name uses the struct name where it originally came from).

lots of new languages are not OOP (e.g. Rust and Go).

Sure, this is not about OOP at all - it boils down to a question of "making already existing methods work seamlessly with your very own structs". If that's a non-goal for V, feel free to close this issue.

Btw. I think this could be kind of solved by embedding, but only if there are some guarantees how embedding of structs works in V - namely how the struct preambles, bytes order, paddings, etc. work (disregarding the backend - I'm thinking of JS as the most problematic backend in this regard - see all the issues Nim had and still has with their JS backend).

Btw. does the syntax x := { variable_being_a_struct_S1_instance | field00: 7 field01: 9 } mean "make a struct which embeds variable_being_a_struct_S1_instance as well as fields field00 of type int and field01 of type int? If not, what does the | (pipe character) mean in this case? And why there is no | character in the documentation?

My bad - the doc says it's struct/object modification :wink:.

Will the following call to fun( y ) work once embedding/mixing/whatever gets implemented?

struct S{
  f int
}
struct S2{
  f2 int
  S
}
fn fun( x S ){
  println( x.f )
  return
}
fn ( x S ) meth(){
  println( x.f )
  return
}

y := S2{ .f: 5, .f2: 9 }  // why are the dots in ".f" and ".f2" needed?
fun( y )
y.meth()

(this currently doesn't compile on Linux with the message: /home/user/test15.v:7:2: struct fields cannot contain uppercase letters, use snake_case instead)
And by the way, why are actually dots in .f and in .f2 (in y := S2{ .f: 5, .f2: 9 }) necessary?

They are not necessary, this shouldn't compile.

Not sure about your example yet

Doesn't work in Go:

https://play.golang.org/p/-ii3XEAyo_j

Doesn't work in Go:
https://play.golang.org/p/-ii3XEAyo_j

Right. I thought that at least casting S2 to S would work, but it doesn't:

./prog.go:21:8: cannot convert S2 literal (type S2) to type S

I would actually like to have at least either casting or direct passing working in V.

@spytheman @medvednikov

What happens if s1 and s2 both have a field with the same name?

Compile time error at the place of field's use unless used with a canonical name (canonical name uses the struct name where it originally came from).

Interesting - Go 1.14 implements overlapping methods. So I'd say in the same vein, V should allow it too instead of compile time error - read the original Go proposal for motivation and examples why it's important.

Interesting @dumblob

I'll check out the proposal.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

taojy123 picture taojy123  路  3Comments

vtereshkov picture vtereshkov  路  3Comments

shouji-kazuo picture shouji-kazuo  路  3Comments

choleraehyq picture choleraehyq  路  3Comments

jtkirkpatrick picture jtkirkpatrick  路  3Comments