我正在寻找一种方法,用 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的答案及其评论,对我想要达到的目标提出很多好处。
答案 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
}