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

Proposal: Data (and struct) syntax #2061

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

HoneyryderChuck
Copy link
Contributor

This is a proposal on how to solve the Data and Struct usage, which currently has to be hand-stitched and is often times incomplete (lack of #members, for example). Essentially it boils down to this:

class A < Data(a: Integer, b: String)
end

The generated type will have the corresponding attribute readers, correct initiallize signatures, #members, and all that, as well as correct class innheritance hierarchy (anonymous dynamic class in between A and Data with all the aforementioned methods).

Implementation-wise, this patch reuses code from record attibutes to parse the class members, and provides an exceptional path for classes inheriting from Data (a similar solution could be built for Struct).

I've left this purposedly raw and incomplete to get some early feedback, before I go too far on an unwanted path.

cc @soutaro

@ParadoxV5
Copy link
Contributor

ParadoxV5 commented Oct 15, 2024

Thank you for the kickstart.


Howëver, this design needs more innovation to be suitable for the anonymous superclass-less design that I and many prefer.

Measure = Data.define(:amount, :unit) do
  self::TAU = 6.28r
end

If RBS < shall correspond to Ruby <, then that snippet’s corresponding RBS might have to be (note the unprecedented `=` _superclass_)

class Measure = Data(amount: Integer, unit: String)
  TAU: Rational
end

https://github.com/ruby/rbs/blob/master/docs/data_and_struct.md#type-checking-class-definitions-using-data-and-struct


This idea of RBS DSLs is great 👍.

I suggest making this system extensible for OOP-based DSLs (e.g., Delegate, FFI/Fiddle) to tap in.

The RBS plugin (whatever form it is) provides handling code that generates the corresponding raw signatures (in the same spirit as soutaro/rbs-inline#76).
The user writes this for https://github.com/ffi/ffi/wiki/Structs/234eab91d0ee55ce103d4b92a96a98c6e8e46890#an-example:

class SimpleStruct = FFI::Struct(value: Float)
end

Note that type args (AKA. generics) are in a tuple list (Type,…) while this is in a record map (key: Type,…).
Could reusing square brackets be syntactically compatible?

class A < Data[a: Integer, b: String]
end

This can even open the doors to “heterogenous type args” by combining the thoughts above.
[Integer, String] would be syntax sugar for Array(0: Integer, 1: String) and {a: Integer, b: String} would be Hash(a: Integer, b: String), and they expand to:

# 0: Integer, 1: String
def []: (0) -> Integer
      | (1) -> String
# a: Integer, b: String
def []: (:a) -> Integer
      | (:b) -> String

@soutaro
Copy link
Member

soutaro commented Oct 16, 2024

@HoneyryderChuck Thank you for this PR.

I think there are several things to consider:

  1. I was thinking the Data (and Struct) things are over because rbs-inline provides some support. (And the feature will be merged into RBS gem, hopefully in a few months.)
  2. Having the Data(...) syntax as an extension point may make sense, as @ParadoxV5 said. It's clearly an area to explore to provide better support for FFI, Delegate...

@HoneyryderChuck
Copy link
Contributor Author

Thx for the feedback sofar!

Howëver, this design needs more innovation to be suitable for the anonymous superclass-less design that I and many prefer.

@ParadoxV5 Indeed, this syntax does not address that. I think it puts us a step closer by providing a "middle ground" with an anonymous class with dynamic method injection, but the thing you're asking should be its own separate contribution; namely, RBS needs a syntax for anonymous class which supports patterns such as Error = Class.new(StandardError).

I suggest making this system extensible for OOP-based DSLs (e.g., Delegate, FFI/Fiddle) to tap in.

I find that suggestion interesting. I don't know enough of RBS internals much to propose such a plugin system (I don't think there is one now, right? 🤔 ), but I could see this being useful for cases such as dynamic method injection potentially, i.e. the case you stated with Delegate, where RBS internally wouldn't need to fiddle with the concept of what a "delegated" class is, and that would be left for the library to provide. Not planning yet to solve the delegate issue, and data/struct are core classes (so should be solved in RBS core), but that could be done, as long as there is an agreement on which RBS syntax can be "bailed out" to external plugins (and how).

Could reusing square brackets be syntactically compatible?

It could, I just thought it'd be clearer to distinguish it from the generic declarations. Another idea would be to use brackets, which would map well to how records are typed (but could be confusing for Data/Struct? maybe?):

# generic example
class A < B[C]

# current proposal
class A < Data(a: Integer)

# proposal with brackets
class A < Data{a: Integer}

This can even open the doors to “heterogenous type args” by combining the thoughts above.

Perhaps, didn't want to go that far 😅 but that could be an avenue for tuples/records.

I was thinking the Data (and Struct) things are over because rbs-inline provides some support.

@soutaro rbs-inline does not (yet) support other injected methods beyond #initialize (#members, #deconstruct_keys...), at least that I could think of. It also doesn't seem to fix the anonymous class hierarchy nor the more popular A = Data.define(... syntax issues (whether that's desirable or not, that's a different question). I'd argue there's room for improvement in RBS syntax to support that.

allows declaring and typing variables, inject inherited methods OTTB
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants