简体   繁体   English

如何在 swift 中实现处理可选性的中缀自定义运算符

[英]How to implement a infix custom operator that handles optionality in swift

I'm trying to implement a custom operator for Collections similar to the Elvis operator (?: in kotlin, ?? in swift), but in addition to checking nullability, the operator also checks if the collection is Empty.我正在尝试为 Collections 实现一个自定义运算符,类似于 Elvis 运算符(?:在 kotlin 中,?? 在 swift 中),但除了检查可空性之外,运算符还检查集合是否为空。

However, when I try to use the operator, the code doesn't compile.但是,当我尝试使用运算符时,代码无法编译。 The compiler throws an "ambiguous use of operator" error.编译器抛出“操作符使用不明确”错误。

The implementation of the??的执行?? operator in the swift language seems to be really similar to mine, so I'm a little lost.. Any help to understand why this happens, and how to fix it, will be greatly appreciated. swift 语言中的运算符似乎与我的非常相似,所以我有点迷茫。任何帮助理解为什么会发生这种情况以及如何解决它,将不胜感激。


/// Returns the first argument if it's not null and not empty, otherwise will return the second argument.
infix operator ?/ : NilCoalescingPrecedence

@inlinable func ?/ <T: Collection>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T {
    if let value = optional,
       !value.isEmpty {
        return value
    } else {
        return try defaultValue()
    }
}

@inlinable func ?/ <T: Collection>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T? {
    if let value = optional,
       !value.isEmpty {
        return value
    } else {
        return try defaultValue()
    }
}

func test() {
    let optionalValue: String? = nil
    
    let value1: String = optionalValue ?? "default value" // This works
    let value2: String = optionalValue ?/ "default value" // This works
    let value3: String? = optionalValue ?/ nil // This works
    let value4: String? = optionalValue ?? nil // This works
    let value5: String? = optionalValue ?? "default value" // This works
    let value6: String? = optionalValue ?/ "default value" // This dont compile: Ambiguous use of operator '?/'
}

The standard implementation for the??的标准实现?? operator can be found at: https://github.com/apple/swift/blob/main/stdlib/public/core/Optional.swift , just search for "?? <" in the browser.运算符可以在: https://github.com/apple/swift/blob/main/stdlib/public/core/Optional.swift中找到,只需在浏览器中搜索“?? <”。

Maybe I'm using the wrong approach to solve this problem.也许我使用错误的方法来解决这个问题。 If anyone knows a better solution will be great too.如果有人知道更好的解决方案也会很棒。

Short answer, you can't do that.简短的回答,你不能那样做。 Probably??, provided by swift, works because swift has it's own powers and it treats these situations on it's own.可能??,由 swift 提供,之所以有效,是因为 swift 有自己的权力,它自己处理这些情况。 But for our code, it doesn't work like that.但是对于我们的代码,它不是那样工作的。

What will happen there is:那里会发生什么:

For the expression: let value2: String = optionalValue?/ "default value" .对于表达式: let value2: String = optionalValue?/ "default value"

  • First the compiler will look to the optionalValue parameter and will find 2 methods that accept an optional as first parameter;首先,编译器将查看optionalValue参数,并找到 2 个接受可选作为第一个参数的方法;
  • Then it will look to the second parameter ( defaultValue ) that is a closure returning a non-optional T: Collection instance, and it will filter and match the first operator overload;然后它会查看第二个参数( defaultValue ),它是一个返回非可选T: Collection实例的闭包,它会过滤并匹配第一个运算符重载;
  • The last thing is the return value that is a non-optional T: Collection instance, and the first method is complient;最后一件事是返回值是一个非可选的T: Collection实例,并且第一个方法是兼容的;
  • Success;成功;

For the expression: let value4: String? = optionalValue?/ "default value"对于表达式: let value4: String? = optionalValue?/ "default value" let value4: String? = optionalValue?/ "default value" . let value4: String? = optionalValue?/ "default value"

  • First the compiler will look to the optionalValue parameter and will find 2 methods that accept an optional as first parameter;首先,编译器将查看optionalValue参数,并找到 2 个接受可选作为第一个参数的方法;
  • Then it will look to the second parameter ( defaultValue ) that is a closure returning an optional T: Collection aka Optional<T> where T: Collection instance, and then it will find 2 options for the 2 parameters so far;然后它会查看第二个参数( defaultValue ),它是一个返回可选T: Collection aka Optional<T> where T: Collection instance 的闭包,然后它将找到到目前为止 2 个参数的 2 个选项;
  • At this point it will look for the return type of the function, but it will fail too;此时会寻找function的返回类型,但也会失败;
  • Compiler error;编译器错误;

The reason that it fails is that T: Collection in your code is translated to String .它失败的原因是代码中的T: Collection被转换为String And for the defaultValue return type an non-optional String fits the first and second methods, leading the compiler to be unsure which one it should use.对于defaultValue返回类型,非可选的 String 适合第一种和第二种方法,导致编译器不确定应该使用哪个方法。

let string: String? = "value" let string: String? = "value" and let string: String? = Optional<String>.init("value") let string: String? = "value"let string: String? = Optional<String>.init("value") let string: String? = Optional<String>.init("value") are the same thing for the compiler due to implicit conversions that Swift does. let string: String? = Optional<String>.init("value")对于编译器来说是一样的,因为Swift所做的隐式转换。

So there is not way to make this work from where my knowledge stands right now.所以从我现在的知识来看,没有办法完成这项工作。

It turns out that swift has an attribute called @_disfavoredOverload, when I use it on the second method, everything works as intended.事实证明,swift 有一个名为@_disfavoredOverload 的属性,当我在第二种方法中使用它时,一切都按预期工作。

Now the implementation of the second method is:现在第二种方法的实现是:

@_disfavoredOverload
@inlinable func ?/ <T: Collection>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T? {
    if let value = optional,
       !value.isEmpty {
        return value
    } else {
        return try defaultValue()
    }
}

Discovered this on the swift forum: https://forums.swift.org/t/how-to-implement-a-infix-custom-operator-that-handles-optionality-in-swift/47260/3 Discovered this on the swift forum: https://forums.swift.org/t/how-to-implement-a-infix-custom-operator-that-handles-optionality-in-swift/47260/3

Based on the implementation, optionalValue?/ "default value" will never return nil .根据实现, optionalValue?/ "default value"永远不会返回nil

So you can do one of these:因此,您可以执行以下操作之一:

Don't use optional if you know it will be not optional: (RECOMENDED)如果您知道它不是可选的,请不要使用它:(推荐)

let value6: String = optionalValue ?/ "default value"

Make it optional:使其成为可选:

let value6: String? = (optionalValue ?/ "default value")!

Make it use the second function:使其使用第二个 function:

let anotherOptionalValue: String? = "default value"
let value6: String? = (optionalValue ?/ anotherOptionalValue

Use @_disfavoredOverload on the second function: (PROHABITED)在第二个 function 上使用@_disfavoredOverload :(禁止)

@_disfavoredOverload
@inlinable func ?/ <T: Collection>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T? { ... }

⚠️ Attributes marked with _ is not designed for general use! ⚠️ 标有_的属性不是为一般用途而设计的!


Note:笔记:

Try using type inference .尝试使用类型推断 So your code will be more optimized and the compiler will have fewer issues to worry about.所以你的代码会更加优化,编译器需要担心的问题也会更少。

let value6 = optionalValue ?/ "default value"
let value7 = optionalValue ?/ anotherOptionalValue

Without _disfavoredOverload, your original value6 needed a.some(...) on the RHS to disambiguate.如果没有 _disfavoredOverload,您的原始 value6 需要 RHS 上的 a.some(...) 来消除歧义。

As you said "Maybe I'm using the wrong approach to solve this problem", I would suggest a different kind of solution.正如您所说的“也许我使用错误的方法来解决这个问题”,我会建议一种不同的解决方案。 Instead of:代替:

if let value = optional,
   !value.isEmpty {
    return value
} else {
    return try defaultValue()
}

Let's write this:让我们这样写:

value.ifNotEmpty ?? defaultValue()

Extend collection like so:像这样扩展集合:

extension Collection {
    var ifNotEmpty: Self? {
        isEmpty ? nil : self
    }
}

extension Optional where Wrapped: Collection {
    var isEmpty: Bool {
        map(\.isEmpty) ?? true
    }

    var ifNotEmpty: Self {
        isEmpty ? nil : self
    }
}

Now we don't need the custom operator, and that means the call sites can read like optionalValue.ifNotEmpty?? "default value"现在我们不需要自定义运算符,这意味着调用站点可以读取为optionalValue.ifNotEmpty?? "default value" optionalValue.ifNotEmpty?? "default value" : optionalValue.ifNotEmpty?? "default value"

let optionalValue: String? = ""
let value1: String = optionalValue.ifNotEmpty ?? "default value"
let value3: String? = optionalValue.ifNotEmpty
let value5: String? = optionalValue.ifNotEmpty ?? "default value"

Depending on your preference, maybe this reads more clearly than the?/ operator根据您的喜好,这可能比?/ 运算符更清楚

But you also get to play nicely with unwrapping:但是你也可以很好地展开展开:

if let foo = optionalValue.ifNotEmpty {
    ...
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM