benhatsor
benhatsor

Reputation: 2033

How to pass a case of type "function" as a parameter?

I've been using Codable enums to encode my classes to JSON, and I've hit a roadblock:

enum MyEnum: Codable {
    case case1(data: Data)
    case case2(str: String)
    case case3(url: URL)    
}

// ...

myFunc(myEnumType: MyEnum.case1)

func myFunc(
        myEnumType: MyEnum // ?
    ) -> MyEnum {
                
        switch myEnumType {
        
        case .case1:
            // insert processing...
            return myEnumType(data: ...)
        case .case2:
            // ...
            return myEnumType(str: ...)
        case .case3:
            // ...
            return myEnumType(url: ...)
            
        }
        
        
    }

Because MyEnum.case1 is of type function, I couldn't use MyEnum, so I had to resort to this very ugly solution[1] [2] [3]:

enum MyEnum: Codable {
    case case1(data: Data)
    case case2(str: String)
    case case3(url: URL)    
}

// :(
enum MyEnumType {
    case case1
    case case2
    case case3
}

// ...

func myFunc(
        myEnumType: MyEnumType // :(
    ) -> MyEnum {

My question

How can I pass my case to the function as a parameter?

What I've tried

This is what I don't want:

  1. Very generic type[4] [5]:
typealias MyEnumType<Arg1> = (Arg1) -> MyEnum

As this could just be any function returning MyEnum. This doesn't guarantee it's a case of MyEnum.

  1. Initializing the case:
myFunc(myEnumType: MyEnum.case1(data: dummyData))

I don't want to initialize the case with dummy data. myFunc is the one that's supposed to initialize the case with actual data, then return the initialized case.

  1. Passing the enum:
myFunc(myEnumType: MyEnum)

This is irrelevant: I want to know what case I'm looking at in the function in order to init each enum uniquely.

My ideal solution

Something like:

func myFunc<T>(myEnumType: MyEnum.T) -> MyEnum {

Which ensures the uninitialized case I've passed to the function is indeed a case of the MyEnum enum.

I'm open to changing the function call or the function, as long as I'm not initializing the case or having to redeclare every case.


References

  1. Answer 2: Swift enum property without initializing the enum case with an associated value?
  2. Allow Swift function parameter to be of multiple types
  3. Swift-Generics: "Cannot specialize non-generic type"
  4. How to take a function of any number of parameters of any type as a parameter in Swift
  5. Answer 5: Swift enum property without initializing the enum case with an associated value?

Relevant research I couldn't use:

Upvotes: 2

Views: 75

Answers (1)

Sweeper
Sweeper

Reputation: 274835

In your "ideal solution", you mention a nested type MyEnum.T. This can be generated by a macro. Here is an example implementation:

// declaration:

@attached(member, names: named(T))
public macro RawCases() = #externalMacro(module: "...", type: "RawCases")

// implementation:

enum RawCases: MemberMacro {
    static func expansion(of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] {
        let caseNames = declaration.memberBlock.members
            .compactMap { $0.decl.as(EnumCaseDeclSyntax.self) }
            .flatMap(\.elements)
            .map(\.name)
        return [DeclSyntax(
            try EnumDeclSyntax("enum T") {
                for name in caseNames {
                    "case \(name)"
                }
            }
        )]
    }
}

Usage:

@RawCases
enum MyEnum: Codable {
    case case1(data: Data)
    case case2(str: String)
    case case3(url: URL)
}

func f(_ value: MyEnum.T) -> MyEnum {
    switch value {
        case .case1:
            .case1(data: Data([1, 2, 3]))
        case .case2:
            .case2(str: "Something")
        case .case3:
            .case3(url: URL(filePath: "/foo/bar"))
    }
}

Upvotes: 1

Related Questions