Skip to content

Instantly share code, notes, and snippets.

@daneden
Created January 16, 2022 11:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daneden/a17f57efd275de544791df6b31f845bf to your computer and use it in GitHub Desktop.
Save daneden/a17f57efd275de544791df6b31f845bf to your computer and use it in GitHub Desktop.
struct Fields {
var userFields: Set<User.Fields>?
var tweetFields: Set<Tweet.Fields>?
}
protocol Fetcher {
// Is there a way to do something like this, where I can specify that the `Fields`
// struct must contain only these member types?
func getTweet(fields: Fields<Tweet.Fields, User.Fields>? = nil) async -> Tweet
func getUser(fields: Fields<User.Fields>? = nil) async -> User
}
@fourplusone
Copy link

One could do some trickery with generics (see below), but I think something like func getTweet(userFields: Set<User.Fields>? = nil, ...) is easier to understand and more idiomatic swift.

protocol _UserFields: Hashable {}
extension Never: _UserFields {}
struct User {
    enum Fields: _UserFields {
        case a
        case b
    }
}

protocol _TweetFields: Hashable {}
extension Never: _TweetFields {}
struct Tweet {
    enum Fields: _TweetFields {
        case a
        case b
    }
}

struct Fields<UserFields : _UserFields, TweetFields: _TweetFields> {
  var userFields: Set<UserFields>?
  var tweetFields: Set<TweetFields>?
}

protocol Fetcher {
  func getTweet(fields: Fields<User.Fields, Tweet.Fields>?) async -> Tweet
  func getUser(fields: Fields<User.Fields, Never>?) async -> User
}

@daneden
Copy link
Author

daneden commented Jan 16, 2022

@fourplusone thanks for the reply and suggestion! Separate fields parameters is what I originally had in the function design but some functions could accept up to 5 distinct field types so function calls could get pretty unwieldy.

This helped me think about another way to express it though. Since the function calls with multiple field types usually follow a pattern (a single primary field type such as Tweet.Fields, and then one or more secondary field types for expansions like Users, Polls, Media, etc), I can probably make the secondary field types associated values on the expansions enum. This has two advantages: removes the need for the generic Fields struct to act as a container, and ensures only field types that are allowed with the expected expansions.

Example function call after this change:

let userFields: [User.Fields] = [.pinnedTweetId, .profileImageURL]
let expansions: [User.Expansions] = [.pinnedTweetId(tweetFields: [.createdAt])]
getUser(userId, userFields: userFields, expansions: expansions)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment