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

An initiative on standardizing struct initializers #101

Closed
DJBen opened this issue Oct 4, 2019 · 8 comments
Closed

An initiative on standardizing struct initializers #101

DJBen opened this issue Oct 4, 2019 · 8 comments
Milestone

Comments

@DJBen
Copy link
Collaborator

DJBen commented Oct 4, 2019

As @jsbean commented on issue #98 and in README, to make this project truly usable by other third party projects, we want to expose publicly available initializers, otherwise the structs are not initializable.

Currently most of the structs are public, but lack initializers. Swift auto-synthesizes internal default initializer for us.

We never run into initialization issues that often because our tests uses @testable import, which grant internal level access of the structs. However when I try to use MusicXML as a dependency from a third party project, I get all kinds of

ClassName is inaccessible due to 'internal' protection level

Thus I want to envision a standard of initializers that we all follow, which is of course subject to debate.


  1. Each struct should provide a default public initializer that covers all the parameters in each struct.
    1.1. For optional fields, the public initializer will provide a default value of nil.
    1.2. Alternative initializers can be kept internal for now.

  2. Declare all properties const (by using let instead of var). Unless there are necessary places that requires var (which we should discuss case by case), there are no need for mutability in most of the cases. We currently use var before because we don't want to provide nil to the auto-synthesized initializer. But now we define our default initializer and default optional fields to nil, there are no reason to use var anymore.

Here's an example that sums up the standard in 1 and 2.

public struct Example {
     public let requiredField: String
     public let optionalField: Int?

    public init(requiredField: String, optionalField: Int? = nil) { 
        self.requiredField = requiredField
        self.optionalField = optionalField
    }
}

// Caller
Example(requiredField: "aaa")
Example(requiredField: "aaa", optionalField: 2)
  1. Create initializer tests.
    These tests should use regular import, not @testable import, aiming to verify that each public struct is initializable publicly.

Here's an example.

import XCTest
// Do not use @testable
import MusicXML

class NoteInitializerTests: XCTestCase {
    func testPitch() {
        _ = Pitch(step: .d, octave: 3)
        _ = Pitch(step: .d, alter: 1, octave: 4)
    }
}

If we all agree, we can make improvement toward this direction and enforce how future struct are initialized. I welcome your input and comments.

@jsbean
Copy link
Member

jsbean commented Oct 4, 2019

@DJBen, thanks for this write-up.

Each struct should provide a default public initializer that covers all the parameters in each struct.
1.1. For optional fields, the public initializer will provide a default value of nil.
1.2. Alternative initializers can be kept internal for now.

Completely agree on both counts.

Declare all properties const (by using let instead of var). Unless there are necessary places that requires var (which we should discuss case by case), there are no need for mutability in most of the cases. We currently use var before because we don't want to provide nil to the auto-synthesized initializer. But now we define our default initializer and default optional fields to nil, there are no reason to use var anymore.

Agreed here, as well. This was the initial intention, but we relaxed the mutability just for the ease of testing. I'd say once we start defining these explicit public initializers, we return the properties back to lets.

Create initializer tests. These tests should use regular import, not @testable import, aiming to verify that each public struct is initializable publicly.

This is a great idea. Do you think it's worth testing every possible combination, or just select shorthand cases and a single "all-in" case?


I initially saw the definition of the public API as the last priority, to be done once we verified that the types were correctly defined (i.e., for version 0.5.0). That being said, it's definitely something that can be done continuously and incrementally without issue, so we might as well get started.

Do you imagine doing this by hand, or with some sort of mechanism like Sourcery?

@jsbean
Copy link
Member

jsbean commented Oct 4, 2019

One thing to consider here is the use of attribute groups in MusicXML. MusicXML uses attribute groups that build on top of each other, predominately for visual properties (e.g., <print-style-align>).

We haven't entirely established how we want to deal with these. Currently, they are wrapped up in nested structs (e.g., PrintStyleAlign). In the xml source, these attributes are all inherently at the top level, not "nested" as they are in our Swift source. It makes me shudder to suggest this, but we could use a class hierarchy for these (which would save us some typing), or with protocols.

I do see the visual attributes to be secondary in priority, but it is something we will have to address at some point.

@DJBen
Copy link
Collaborator Author

DJBen commented Oct 4, 2019

This is a great idea. Do you think it's worth testing every possible combination, or just select shorthand cases and a single "all-in" case?

I messed around a bit in my experimental PR and found an "all-in" case achieves best effect with minimal work.

Do you imagine doing this by hand, or with some sort of mechanism like Sourcery?

I'll start by doing this by hand; I am also open to any automation tool. Currently if there's a long list of attributes I'll write Regex to help myself.

We haven't entirely established how we want to deal with these. Currently, they are wrapped up in nested structs (e.g., PrintStyleAlign). In the xml source, these attributes are all inherently at the top level, not "nested" as they are in our Swift source. It makes me shudder to suggest this, but we could use a class hierarchy for these (which would save us some typing), or with protocols.

I strongly agree that we should nest top level attributes into structs. The reason that xml has so many top level attributes is precisely due to its inability to nest these attributes.
In my PR to fix the Positions, I invoke the nested struct's init(decoder:) in the parent struct init(decoder:) to nest the attributes effortlessly. See code How do you think of this approach?

@jsbean
Copy link
Member

jsbean commented Oct 4, 2019

I really like the direction of implementing and calling the init(decoder:) for attribute groups like Position.

Is it possible to do the same for the Font properties?

@jsbean jsbean closed this as completed Oct 4, 2019
@jsbean jsbean reopened this Oct 4, 2019
@jsbean
Copy link
Member

jsbean commented Oct 4, 2019

(Whoops, ignore the accidental closure)

@DJBen
Copy link
Collaborator Author

DJBen commented Oct 4, 2019

Is it possible to do the same for the Font properties?

Definitely

@jsbean jsbean added this to the 0.4.0 milestone Oct 13, 2019
@jsbean
Copy link
Member

jsbean commented Oct 17, 2019

@DJBen, my intuition is that this is relatively well implemented. Are there any cases otherwise? If no, then I'd say we can close this!

@DJBen
Copy link
Collaborator Author

DJBen commented Oct 17, 2019 via email

@jsbean jsbean closed this as completed Oct 17, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants