0

We have enum:

enum Line {
    case horizontal(CGFloat)
    case vertical(CGFloat)
    case diagonal(CGFloat, CGFloat)
}

by default we are able to use 3 ways to get value from ValueAssociatedEnum

But all these methods looks terrible.

IMHO.

Question is:

Is it possible to create generic extension for enum to use this like syntax:

Sample 1:

let lineH = Line.horizontal(10)

if let val = lineH.is(Line.horizontal) {
     print("lineHorizontalValue: \(val)")             // lineHorizontalValue: 10
}

Sample 2:

let lineD = diagonal(10, 20)

if let (point1, point2) = lineD.is(Line.diagonal) {
     print("lineDiagonalValue: \(point1), \(point2)") // lineDiagonalValue 10, 20
}
Andrew_STOP_RU_WAR_IN_UA
  • 9,318
  • 5
  • 65
  • 101
  • `if case let .horizontal(val) = lineH` is not objectively worse than `if let val = lineH.is(Line.horizontal)`. What does "generic extension" mean? –  Apr 01 '23 at 01:15
  • @Jessy [What does "generic extension" mean?] - this mean that I will be able to use this extension on ANY enum. | [is not objectively worse than] - I believe it is. Because of my solution have more linear code, more short code and more conventional code. `Case` in case of enum associated value have too specific syntax, but my syntax looks more natural for Swift as using func with parameter is more wide used than syntax created specially for enum associated values. – Andrew_STOP_RU_WAR_IN_UA Apr 01 '23 at 01:21
  • Show an example of where you can't use the language's syntax to do what you want. To me, it just looks like you're trying to write in another language, but not actually asking about how to do something that isn't already a language feature. –  Apr 01 '23 at 01:23
  • @Jessy problem not in that I cannot use default language feature. If I am able to do my code more linear and more simple, why do not do this in my projects? Standard syntax not always is ideal. Standard ways of doing some things not always looks nice. That's why people create something like this: https://github.com/21GramConsulting/Beton/blob/develop/Sources/Beton/WtfOperator/WtfOperator%2BthrowIfOptional.swift . And that's why I'm interested in such generic extension like I have asked in my question. – Andrew_STOP_RU_WAR_IN_UA Apr 01 '23 at 01:28
  • 1
    That code is not good, but it adds a feature that I also use—just in a bad way. You're just trying to change the language's syntax. It can't be done, and this question is not useful. The closest you can get is https://stackoverflow.com/a/64808008/652038 –  Apr 01 '23 at 01:31
  • @Jessy will investigate your code. Thanks. – Andrew_STOP_RU_WAR_IN_UA Apr 01 '23 at 01:36
  • Have you considered making `Line` a structure with en enum as a property? – trndjc Apr 01 '23 at 01:43
  • @trndjc no, as main idea is to do more simple and user-friendly access to value located in EnumAssociatedValues than Swift have by default. Main idea was to create generic extension. Extension that will work for any enum with associated values, not for `Line` enum only. `Line` is just created as a sample, not more. So Jessy is right, I'm just trying "to change Swift syntax"... – Andrew_STOP_RU_WAR_IN_UA Apr 01 '23 at 01:50
  • 1
    Your proposed syntax could be ambiguous. Enum cases can have duplicate names as long as they have different associated values. What would `lineH.is(Line.horizontal)` return when `Line` has two cases called `horizontal`, one with one `Int` and another with 2 `Int`s associated? The only thing It can safely return is `Any?` as far as I can see. Is that going to be useful to you? I think not. – Sweeper Apr 01 '23 at 02:10
  • 1
    @Sweeper you're right. In case of 2 cases with similar names this will not work =( But even of this i have learned a lot today :) As example from code of Jessy – Andrew_STOP_RU_WAR_IN_UA Apr 01 '23 at 02:13

2 Answers2

-1

I have read lot of messages that it is impossible to have different return types for same function, but this is possible as you see here :)


Based on Jessy's code :

CAUTION:

  • this code is the wrong way to work with Enum Associated Value's(!!!). Better to use standard syntax.
  • this code works slower than standard syntax.
  • this code will not work in case of you have few enum cases with the same name, but different associated value types.

SO NEVER USE SUCH CODE IN PRODUCTION

Instead of this use standard syntax


This was useless, but still, this was a really interesting experiment. Thanks a lot, Jessy & Sweeper.

usage sample:

 enum Line: isAble {
     case horizontal(CGFloat)
     case vertical(CGFloat)
     case diagonal(CGFloat, CGFloat)
 }


 let lineD = Line.diagonal(10, 20)
 
 // will print "lineDiagonalValue: (10, 20)"
 if let (val1, val2) = lineD.is(Line.diagonal) {
      print("lineDiagonalValue: (\(val1), \(val2))") 
 }
 

 if let val = lineH.is(Line.horizontal) {
      print("this code will never work")
 }
import Foundation

/* usage sample
 enum Line: isAble {
     case horizontal(CGFloat)
     case vertical(CGFloat)
 }


 let lineH = Line.horizontal(10)
 
 // will print "lineHorizontalValue: 10"
 if let val = lineH.is(Line.horizontal) {
      print("lineHorizontalValue: \(val)") 
 }
 

 if let val = lineH.is(Line.horizontal) {
      print("this code will never work")
 }
*/

protocol isAble { }

extension isAble {
    public func `is`<T>( _ val: (T) -> isAble) -> T? {
        return Mirror.associatedValue(of: self, ifCase: val)
    }

    public func haveValue<T>( ofCase val: (T) -> isAble) -> Bool {
        return Mirror.associatedValue(of: self, ifCase: val) != nil
    }
}


/// Match `enum` cases with associated values, while disregarding the values themselves.
/// - Parameter case: Looks like `Enum.case`.
public func ~= <Enum: Equatable, AssociatedValue>(
    case: (AssociatedValue) -> Enum,
    instance: Enum
) -> Bool {
    Mirror.associatedValue(of: instance, ifCase: `case`) != nil
}

/// Match `enum` cases with associated values, while disregarding the values themselves.
/// - Parameter case: Looks like `Enum.case`.
public func ~= <Enum, AssociatedValue>(
    case: (AssociatedValue) -> Enum,
    instance: Enum
) -> Bool {
    Mirror.associatedValue(of: instance, ifCase: `case`) != nil
}

/// Match non-`Equatable` `enum` cases without associated values.
public func ~= <Enum>(pattern: Enum, instance: Enum) -> Bool {
    guard (
        [pattern, instance].allSatisfy {
            let mirror = Mirror(reflecting: $0)
            return mirror.displayStyle == .enum && mirror.children.isEmpty
        }
    ) else { return false }
    
    return .equate(pattern, to: instance) { "\($0)" }
}


public extension Mirror {
    /// Get an `enum` case's `associatedValue`.
    static func associatedValue<AssociatedValue>(
        of subject: Any,
        _: AssociatedValue.Type = AssociatedValue.self
    ) -> AssociatedValue? {
        guard let childValue = Self(reflecting: subject).children.first?.value
        else { return nil }
        
        if let associatedValue = childValue as? AssociatedValue {
            return associatedValue
        }
        
        let labeledAssociatedValue = Self(reflecting: childValue).children.first
        return labeledAssociatedValue?.value as? AssociatedValue
    }
    
    /// Get an `enum` case's `associatedValue`.
    /// - Parameter case: Looks like `Enum.case`.
    static func associatedValue<Enum: Equatable, AssociatedValue>(
        of instance: Enum,
        ifCase case: (AssociatedValue) throws -> Enum
    ) rethrows -> AssociatedValue? {
        try associatedValue(of: instance)
            .filter { try `case`($0) == instance }
    }
    
    /// Get an `enum` case's `associatedValue`.
    /// - Parameter case: Looks like `Enum.case`.
    static func associatedValue<Enum, AssociatedValue>(
        of instance: Enum,
        ifCase case: (AssociatedValue) throws -> Enum
    ) rethrows -> AssociatedValue? {
        try associatedValue(of: instance).filter {
            .equate(try `case`($0), to: instance) {
                Self(reflecting: $0).children.first?.label
            }
        }
    }
}

public extension Optional {
    /// Transform `.some` into `.none`, if a condition fails.
    /// - Parameters:
    ///   - isSome: The condition that will result in `nil`, when evaluated to `false`.
    func filter(_ isSome: (Wrapped) throws -> Bool) rethrows -> Self {
        try flatMap { try isSome($0) ? $0 : nil }
    }
}

public extension Equatable {
    /// Equate two values using a closure.
    static func equate<Wrapped, Equatable: Swift.Equatable>(
        _ optional0: Wrapped?, to optional1: Wrapped?,
        using transform: (Wrapped) throws -> Equatable
    ) rethrows -> Bool {
        try optional0.map(transform) == optional1.map(transform)
    }
}
Andrew_STOP_RU_WAR_IN_UA
  • 9,318
  • 5
  • 65
  • 101
-1

you can't have different return types for same function, what you need will look like:

enum Line {
    case horizontal(CGFloat)
    case vertical(CGFloat)
    case diagonal(CGFloat, CGFloat)
}

extension Line {
    var asDiagonalValues : (CGFloat, CGFloat)? {
        switch self {
        case .diagonal(let p1, let p2): return (p1,p2)
        default: return nil
        }
    }
    
    var asVertialValue : CGFloat? {
        switch self {
        case .vertical(let p1): return p1
        default: return nil
        }
    }
}

You may also need operator like:

func ~= ( left: Line, right: Line) -> Bool {
    switch (left,right) {
    case (.horizontal(_), .horizontal(_)): return true
    case (.vertical(_), .vertical(_)): return true
    case (.diagonal(_,_), .diagonal(_,_)): return true
    default: return false
    }
}

Line.horizontal(20) ~= Line.horizontal(30) // returns true

and with extension like this:

extension Line {
    var asUno: CGFloat? {
        switch self {
        case .vertical(let p): return p
        case .horizontal(let p): return p
        default: return nil
        }
    }
    
    var asDuo: (CGFloat,CGFloat)? {
        switch self {
        case .diagonal(let p1, let p2): return (p1,p2)
        default: return nil
        }
    }
}

you can write code like this:

var line = Line.diagonal(1, 2)

if line ~= Line.diagonal(0,0), let vals = line.asDuo {
    let (p1,p2) = vals
    
    print(p1,p2)
}
Vincenso
  • 516
  • 4
  • 8