Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TODO] Nim should allow nested types #7449

Closed
timotheecour opened this issue Mar 30, 2018 · 8 comments
Closed

[TODO] Nim should allow nested types #7449

timotheecour opened this issue Mar 30, 2018 · 8 comments

Comments

@timotheecour
Copy link
Member

timotheecour commented Mar 30, 2018

(if nested types are already supported, sorry about the noise and please let me know how!)

rationale

  • tons of languages support nested types, (eg C++, D, C#, swift, python ...)
  • It provides a level of encapsulation
  • it avoids polluting the top-level symbol table.

no nested types makes wrapping libraries in other languages problematic

eg:

test.cpp:

struct Foo {
  struct Bar {
  };
};
struct Bar {
};

test.nim: generated via ~/.nimble/bin/c2nim tests.cpp
```nim
type
  Foo* {.bycopy.} = object
  
  Bar* {.bycopy.} = object
  
  Bar* {.bycopy.} = object

which gives invalid Nim file.

These name clashes are unavoidable in large C++ libraries (or other languages), and workarounds seem very costly (eg changing C++ code? custom handling in nim wrapper?)

other problematic example

template<typename T>
struct Foo {
  template<typename U>
  struct Bar {
  };
};

no nested types makes using DSL's problematic, eg protocol buffers:

message FooBar {
}
message Foo {
  Bar bar = 1;
  message Bar {
    int32 a = 1;
  }
}

https://github.com/PMunch/protobuf-nim generates types Foo, Foobar (for submessage) and FooBar (top-level), which clashes and produces invalid code.
NOTE: these protobufs could be from external third party libraries over which we have no control

Proposed syntax for nested types in Nim:

type 
  Foo = object
    age: int
    type 
      Bar = object
        name: string
    Signals = enum
      sigQuit = 3, sigAbort = 6

var a:Foo
var b:Foo.Bar
@mratsim
Copy link
Collaborator

mratsim commented Mar 31, 2018

What's wrong with?

type
  Bar = object
    name*: string

  Foo* = object
    age*: int
    bar*: Bar

var a:Foo
var b:Foo.bar

Notice that Bar is not exported but its fields are (or you can use "accessors" procs)

Now there might be some need on the importcpp side but for pure Nim I think my solution is fine.

@dom96
Copy link
Contributor

dom96 commented Apr 1, 2018

TIL you can declare a variable's type using the field of another type. Nice.

@timotheecour
Copy link
Member Author

timotheecour commented Apr 2, 2018

  • adding a field bar to Foo changes the ABI, notably Foo.sizeof increases by Bar.sizeof. This is not the case in all the other languages that allow nested types, and this pattern completely changes the meaning when porting foreign libraries

  • these other languages allow (and use!) allow nested type alias, eg: (in D) struct Foo(T) {alias This=Foo;} (useful in more complex use cases); with suggested pattern this would not work
    This pattern is very common, for a C++ example see: Member types in http://en.cppreference.com/w/cpp/container/vector

  • this doesn't address name clashes within a module I mentioned in the top-level post

  • this would not work with automatic wrapping of foreign libraries that use nested types ; and would lead to a lot of pain if doing manual wrapping

  • this also wouldn't work with DSL's (eg protocol buffer example I mentioned); the generated types will clash (they'd be generated in the same module)

  • What would be downsides of the syntax I proposed?

  • Side question: why is var b:Foo.bar syntax even allowed? that just leads to confusion; instead var b:Foo.bar.type should be required IMO

@krux02
Copy link
Contributor

krux02 commented Apr 4, 2018

@timotheecour you are totally right import macros should not be allowed. But I am against your proposal of subtyping. I don't think that your syntax proposal will be accepted, becaus the syntax is something that probably won't get such drastic changes anymore. But that doesn't mean that a pattern that feels like subtyping won't be possible. You can already get something like subtying in Nim.

type
  Foo = object
  FooBar = object

template Bar(_: typedesc[Foo]): untyped = FooBar

var a: Foo
var b: Foo.Bar
var c: a.type.Bar

But you would be required to resolve conflicting names manually when wrapping libraries. In this example I did it with prefixing the partent type to the subtype. Then your example with generics, I am afraid I could not get it to work with a template like the template above. But luckyly in practice that is rarely a problem

type
  Foo[T] = object
    ft: T

  FooBar[T,U] = object
    bt: T
    bu: U

proc buzz[T,U](self: Foo[T], something: U): FooBar[T,U] =
  result.bt = self.ft
  result.bu = something

var a: Foo[int]
let b = a.buzz

FooBar is not a subtype here. But it solves the same purpose as the subtype, just with less nesting involved. Sometimes you just need to think a little bit different when learning a new language, not everything can be mapped 1:1. Especially subtypes.

@andreaferretti
Copy link
Collaborator

@timotheecour

adding a field bar to Foo changes the ABI, notably Foo.sizeof increases by Bar.sizeof. This is not the case in all the other languages that allow nested types, and this pattern completely changes the meaning when porting foreign libraries

I am not sure I understand - doesn't your protobuf example embed Bar inside Foo (hence increases Foo's size)?

In any case, I would not introduce such a breaking change at this point, given that its advantages are somewhat limited

@zah
Copy link
Member

zah commented Apr 4, 2018

Regarding creating type associations involving generic types, the following should work:

import typetraits

type
  Foo[T] = object

  Bar[T] = object

  Baz[T, U] = object

  BazAlias[T] = Baz[int, T]
  
template matchingBar[T](f: type Foo[T]): typedesc = Bar[T]
template unboundBaz(f: type Foo): typedesc = Baz
template partiallyBoundBaz[T](f: type Foo[T]): typedesc = BazAlias[T]

var 
  x1: Foo[int].matchingBar
  x2: Foo[string].unboundBaz[float]
  x3: Foo[float].partiallyBoundBaz

echo x1.type.name
echo x2.type.name
echo x3.type.name

The expected output should be:

Bar[int]
Bar[float]
Baz[int,float]

I consider it a bug that it's currently failing. There is an issue with resolving bound types names inside template bodies.

@krux02
Copy link
Contributor

krux02 commented Apr 4, 2018

@zah well you did not properly handle the nested type with the generic. Each instance of Foo[T] has it's onwn instance of all subtypes. Meaning Foo[int].Bar[float] is a different type than Foo[float].Bar[float]. Therefore the inner types needs to have two type parameters. Foo[int].Bar[float] should instanciate FooBar[int,float]

@Araq
Copy link
Member

Araq commented Sep 2, 2018

Not gonna happen anytime soon, sorry. We need to focus on stability, not on more features, no matter how good they might look.

@Araq Araq closed this as completed Sep 2, 2018
@timotheecour timotheecour changed the title Nim should allow nested types [TODO] Nim should allow nested types Sep 3, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants