Skip to content
This repository was archived by the owner on Jan 10, 2023. It is now read-only.

Alternative implementation of StaticStructural #10

Closed

Conversation

shabalind
Copy link
Contributor

@shabalind shabalind commented Aug 5, 2020

This PR proposes an alternative implementation of StaticStructural from #9:

  • We add KeyPaths and BaseType to the structural representations.
  • StaticStructural is implemented as a regular structural of a zero value.
  • Additionally, as an example we show how implement allKeyPaths on top of it.

In contrast to #9 this approach does not require duplication of the API to provide new representation types for static counterparts.

@shabalind shabalind requested a review from saeta August 5, 2020 14:51
CustomEquatable: Point14 (reference) 25.0 ns ± 40.39 % 1000000
CustomEquatable: Point15 (generic) 1145.0 ns ± 20.39 % 1000000
CustomEquatable: Point15 (reference) 25.0 ns ± 38.51 % 1000000
CustomEquatable: Point16 (generic) 1213.0 ns ± 12.65 % 1000000
Copy link
Contributor Author

@shabalind shabalind Aug 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runtime performance is degraded up to 10x in some cases due to addition of KeyPaths. It's unclear how much effort will it take to bring it back down while preserving KeyPaths in the API.

StructuralCons<
StructuralProperty<Point3>,
StructuralProperty<LabeledPoint3, Point3, Point3>,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the fact that we don't hide property wrapper types, unlike KeyPaths we need to have 2 type parameters for value: one for wrapped and another for unwrapped value. This has unfortunate an unpleasant complexity cost.

@shabalind
Copy link
Contributor Author

/cc @dabrahams

public var name: String
public var value: Value
public var isMutable: Bool
public var value: WrappedValue
Copy link
Contributor Author

@shabalind shabalind Aug 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we can't get name out of KeyPath in a portable manner (there is a macOS-only solution that relies on NSExpression). This is intended because KeyPaths can express arbitrary multi-step field accesses.

What's even more unsettling is that we use StructuralProperty for both StructuralStruct properties and StructuralEnum associated values. Associated values don't work with KeyPaths at all, so if we unify this even further by removing name, we'll have to add yet another structural representation type StructuralAssociatedValue which is nearly identical to StructuralProperty, only with a name instead of keyPath.

Copy link
Contributor

@saeta saeta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work here @shabalind ! Thanks for this PR. I think this is really good, and probably better than having a doubling of the API surface. That said, some of the more interactions with KeyPaths (in particular WrappedValue) are quite unfortunate.

public struct StructuralStruct<Properties> {
public var type: Any.Type?
public struct StructuralStruct<BaseType, Properties> {
public var type: BaseType.Type?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is type Optional<BaseType.Type> and not simply BaseType.Type? (Note: I realize this was the case before your change too.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you look at implementation of Zero we need to be able to create an instance StructuralStruct based on known type parameters only.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But don't we now have BaseType? Couldn't we do something like:

extension StructuralStruct: Zero where Properties: Zero {
    public static var zero: Self {
        return StructuralStruct(type: BaseType.self, Properties.zero)
    }
}

self.type = type
self.properties = properties
}
}

/// Structural representation of a Swift property.
public struct StructuralProperty<Value> {
public struct StructuralProperty<BaseType, Value, WrappedValue> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document Value vs WrappedValue. (I only understood the distinction when looking through the examples and seeing one using property wrappers.)

Agreed that this is some unfortunate complexity. :-(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comment in b20fb72

}

protocol _CollectKeyPaths {
func collectKeyPaths(into keyPaths: inout [AnyKeyPath])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about:

Suggested change
func collectKeyPaths(into keyPaths: inout [AnyKeyPath])
func collectKeyPaths(into keyPaths: inout [PartialKeyPath<Self>])

or:

Suggested change
func collectKeyPaths(into keyPaths: inout [AnyKeyPath])
associatedtype BaseType
func collectKeyPaths(into keyPaths: inout [PartialKeyPath<BaseType>])

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This complicated implementation of _CollectKeyPath because we don't have BaseType for StructuralCons and StructuralEmpty. Adding them just for this use case seems like a massive overkill.

Copy link
Contributor

@saeta saeta Aug 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that adding BaseType to StructuralCons doesn't make sense (although it can likely be conditionally inferred with the "BaseTypeProtocol" I've used in related PRs), and StructuralEmpty of course "should" be Never which neatly solves that problem.

I say "should" because I'm not sure how I feel about it yet. While it has some aspect of feeling more regular, I'm not sure it substantially simplifies use of SGP in practice. But this is only a weak prior.

name time std iterations
---------------------------------------------------------------------------------
Additive: Point1 (specialized) 28.0 ns ± 52.30 % 1000000
Additive: Point1 (generic) 124.0 ns ± 32.32 % 1000000
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: seems like we've fallen off an optimizer performance cliff. :-(

@shabalind
Copy link
Contributor Author

This has been an interesting experiment, but we decided to postpone this for now. We might revisit it later.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants