将`if / guard let`替换为单个函数指令中的两个值

时间:2017-05-03 14:17:09

标签: swift functional-programming

我正在寻找一种方法,用 Swift 中的功能对象替换if let(或guard let)测试。我只是找不到办法去做,也许这是不可能的,但我很好奇。

所以我有这段代码:

struct Example {
    var first: String? = "First"
    var second: String? = "Second"

    var concat: String? {
        guard let first = first, let second = second else {
            return nil
        }

        return first + second
    }
}

var example = Example()
example.concat // FirstSecond

example.first = nil
example.concat // nil

我想要的是concat变量为nil,如果第一个或第二个变量为nil。这就是上面的代码正在做的事情。我正在考虑如何使它功能性 *彩虹*。

我想出了这个,但我不满意,因为我需要在flatMap上显式的参数名称,我想知道是否有更清晰的解决方案(暗示链接地图或类似的东西):< / p>

struct Example {
    var first: String? = "First"
    var second: String? = "Second"

    var concat: String? {
        return first.flatMap { f in second.map { s in f + s } }
    }
}

var example = Example()
example.concat // FirstSecond

example.first = nil
example.concat // nil

我想到了元组,但它总是意味着使用像Array这样的中间类型,对我来说感觉不太干净。

编辑1:最后,请注意此示例正在使用String,但我想找到适用于任何Optional类型的解决方案。

我的理想解决方案将如下所示:

var concat: String? {
    return (first, second).map { $0 + $1 }
}

但这是不可能的,因为我们无法定义元组的函数。

编辑2:我得到的最接近(从我的理想解决方案)代码是定义一个全局函数(不是很干净),如下所示:

func opMap<T>(_ tuple: (T?, T?), transform: (T,T) -> T?) -> T? {
    guard let one = tuple.0, let two = tuple.1 else { return nil }
    return transform(one, two)
}

var concat: String? {
    return opMap((first, second)) { $0 + $1 }
}

此处出现另一个限制,我只能使用值对。我理想的解决方案也适用于任意数量的值。

编辑3:正如@Hamish在评论中所提出的,这是使用Optional扩展解决问题的一种有趣方式:

extension Optional {
    func map(with: Wrapped?, transform: (Wrapped, Wrapped) -> Wrapped?) -> Wrapped? {
        guard case .some(let first) = self, let second = with else { return nil }
        return transform(first, second)
    }
}

var concat: String? {
    return first.map(with: second) { $0 + $1 }
}

编辑4:(是的,我喜欢编辑)@Martin R提出了对先前解决方案的改进,它更接近我想要的(因为我们有第一,第二和结果的独立类型) ,见下文:

extension Optional {
    func map<T, S>(with: T?, transform: (Wrapped, T) -> S?) -> S? {
        guard case .some(let first) = self, let second = with else { return nil }
        return transform(first, second)
    }
}

TRUE LAST EDIT:我强烈建议您查看@Rob Napier的答案及其评论,对我想要达到的目标提出很多好处。

5 个答案:

答案 0 :(得分:3)

我认为有一个非常简单的解决方案:

return ([first, second] as? [String])?.joined()

它是如何运作的?

仅当数组不包含任何选项时,as?强制转换为非可选数组才会成功。这基本上就是你要检查的内容。

如果没有可选项,joined()将连接非可选项。

答案 1 :(得分:3)

虽然我认为原始代码非常好Swift,以及你应该使用什么(并且它没有任何功能),你想要的FP工具在Haskell中被称为sequence 。你在这里建造的是一个单子(我知道&#34; monad&#34;总是一个令人困惑的对话的开始,但这正是你正在构建的)。如果所有元素都有值,则需要一个具有值的表达式,如果任何元素没有值,则需要没有值。这正是monad非常常用的。

所以让我们构建sequence。遗憾的是,这与Swift的sequence冲突,但我并不是真的建议您为此目的使用此工具。 if-let是斯威夫特中一个更强大的单子;它实际上非常接近Haskell的记号。

// I'd never build `sequence` this way. I'd build it with a for-loop in Swift, but 
// to stay super functional...
func sequence<T>(_ elements: [T?]) -> [T]? {
    let result = elements.flatMap{$0}
    return result.count == elements.count ? result : nil
}

好的,我们该怎么做?嗯,这一切都非常普遍(好的FP是)。对于作为一百个元素的两个元素,它应该同样有效。所以数组就是这个工具。 reduce让我们将数组转换为值:

var concat: String? {
    return sequence([first, second])?.reduce("", +)
}

答案 2 :(得分:1)

let a: String? = "A"
let b: String? = "B"
let c: String? = "C"

a.concat(b)         // concat #1
a.concat([b,c])     // concat #2
[a,b,c].concat()    // concat #3
[a,b].concat(c)     // concat #4
[a,b].concat([b,c]) // concat #5

要为concat或任何其他类型设置此类String功能,请确保您的类型符合Concatable

protocol Concatable {
    static func +(lhs: Self, rhs: Self) -> Self
}

extension String: Concatable {}

其余的实施:

extension Optional where Wrapped: Concatable {
    // concat #1
    func concat(_ value: Optional) -> Optional {
        guard let first = self, let second = value else { return nil }
        return first + second
    }
    // concat #2
    func concat(_ values: [Optional]) -> Optional {
        guard let first = self, let second = values.concat() else { return nil }
        return first + second
    }
}

protocol OptionalType {
    associatedtype T
    var optional: T? { get }
}

extension Optional: OptionalType {
    var optional: Wrapped? {
        return self
    }
}

extension Array where Element: OptionalType, Element.T: Concatable {
    // concat #3
    func concat() -> Element.T? {
        guard count > 0, contains(where: { $0.optional == nil }) == false else { return nil }
        let array = map({ $0.optional! })
        return array.dropFirst().reduce(array[0], +)
    }
    // concat #4
    func concat(_ value: Element.T?) -> Element.T? {
        return concat().concat(value)
    }
    // concat #5
    func concat(_ values: [Element.T?]) -> Element.T? {
        return concat().concat(values.concat())
    }
}

答案 3 :(得分:0)

我不确定这是否完全符合您的要求,但我认为这是正确的精神

func apply<T>(_ x: T?, y: T?, f: (T, T) -> T?) -> T?
{
    if let x = x, let y = y
    {
        return f(x, y)
    }
    return nil
}

var first: String? = "First"
var second: String? = "Second"

var concat: String? {
    return apply(first, second, +)
}

答案 4 :(得分:0)

您似乎希望简化代码中的使用点。

考虑到这一点,我建议重载到+运算符。不幸的是,我找不到+的基础协议,所以我自己做了:

protocol Addable {
    static func +(lhs: Self, rhs: Self) -> Self
}

func +<T: Addable>(lhs: T?, rhs: T?) -> T? {
    guard let lhs = lhs, let rhs = rhs else {
        return nil
    }
    return lhs + rhs
}

extension String: Addable { }
extension Int: Addable { }
extension Float: Addable { }

let test1: String = "1testing"
let test2: String? = "2testing"
let test3: String? = nil

let realTest = test1 + test2 // 1testing2testing
let nilTest = test1 + test3 // nil

let intTest1: Int = 1
let intTest2: Int? = 2
let intTest3: Int? = nil

let intRealTest = intTest1 + intTest2 // 3
let intNilTest = intTest1 + intTest3 // nil

不幸的是,您无法扩展协议(例如IntegerArithmetic)以遵守其他协议,因此您必须为所需的所有类型添加依从性声明,但它与正常使用相同,同时还允许自定义类型遵循

或者,这3个将处理+:

的主要用例
func +(lhs: String?, rhs: String?) -> String? {
    guard let rhs = rhs else {
        return nil
    }
    return lhs?.appending(rhs)
}

func +<T: IntegerArithmetic>(lhs: T?, rhs: T?) -> T? {
    guard let lhs = lhs, let rhs = rhs else {
        return nil
    }
    return lhs + rhs
}

func +<T: FloatingPoint>(lhs: T?, rhs: T?) -> T? {
    guard let lhs = lhs, let rhs = rhs else {
        return nil
    }
    return lhs + rhs
}