我想改变的主要只是Set的结构,如果下面的一些代码可能会获得类型推断,那完全没问题,但整体的emptySet和useSet必须保持不变。
struct Set {
var elements: [String: Any] = [:]
}
let emptySet: () -> Set = {
func makeSet(_ xs: [Int]) -> Set {
func contains(_ i: Int) -> Bool {
return xs.contains(i)
}
return Set(elements: [
"insert": { (i: Int) in
if contains(i) {
return makeSet(xs)
} else {
return makeSet([i] + xs)
}
},
"member": contains,
"size": { () -> Int in
return xs.count
}
])
}
return makeSet([])
}
// example client
func useSets() -> Int {
let s1 = emptySet()
let s2 = (s1.elements["insert"] as! (Int) -> Set)(34)
let s3 = (s2.elements["insert"] as! (Int) -> Set)(34)
let s4 = (s3.elements["insert"] as! (Int) -> Set)(19)
if (s4.elements["member"] as! (Int) -> Bool)(42) {
return 99
} else if (s4.elements["member"] as! (Int) -> Bool)(19) {
return 17 + (s3.elements["size"] as! () -> Int)()
} else {
return 0
}
}
// 18
let a = useSets()
我正在重构 Set 结构,以使用 DictSet 协议的面向协议的方法。该协议定义了三个方法:insert、member 和 size,每个方法都返回 MyClosureType 类型的闭包。 DictImpl 结构符合 DictSet 并使用这些闭包来实现其功能。与之前的实现相比,这种方法提供了更好的类型安全性和封装性。
这是新代码...只要让它工作:)
typealias MyClosureType = (Int) -> Any
protocol DictSet {
func insert() -> MyClosureType
func member() -> MyClosureType
func size() -> MyClosureType
}
struct DictImpl: DictSet {
var elements: [String : MyClosureType] = [:]
init() {
elements["insert"] = insert()
elements["member"] = member()
elements["size"] = size()
}
func insert() -> MyClosureType {
<#code#>
}
func member() -> MyClosureType {
<#code#>
}
func size() -> MyClosureType {
<#code#>
}
}
问题在于我如何将闭包分配给 DictImpl 中的元素字典。也许我应该分配闭包本身,而不是调用它们的结果。对这个东西还是新手。
编辑:我想保留字典如何在emptySet中实现的想法。我知道 useSets 在新的实现之后不会那么冗长,但是 let 和 if 语句必须在那里。我只是不知道新代码将如何影响emptySet内的字典(或如何实现它)
我相信我知道您想要实现的 FP 练习。这里的目标(如果我对你所做的事情是正确的)是演示存储功能,而不是实际存储数据。因此,值本身永远不在集合中;只是它们是否被收容。由于我将在最后演示的原因,这可能非常强大,因此这不是纯粹的学术练习。
但是,您最初的代码处理此问题的方式不正确。以下是您如何在 Swift 中实现这一目标,尽享函数式编程的荣耀。
struct FunctionSet {
static let empty = Self(member: { _ in false }, size: {0})
let member: (Int) -> Bool
let size: () -> Int
func insert(_ x: Int) -> Self {
member(x) ? self : Self(member: { x == $0 || member($0) },
size: { size() + 1 })
}
}
您的
useSets
代码仍然可以按您的预期工作,只是没有所有as!
:
func useSets() -> Int {
let s1 = FunctionSet.empty
let s2 = s1.insert(34)
let s3 = s2.insert(34)
let s4 = s3.insert(19)
if s4.member(42) {
return 99
} else if s4.member(19) {
return 17 + s3.size()
} else {
return 0
}
}
关键点是FunctionSet只包含函数。它实际上并不包含 34 和 19。它只包含一个函数 (
member
),如果包含该值,该函数将返回 true。
如果集合可能非常大并且由规则定义,那么这是一个有用的工具。为了探讨这一点,让我定义一个稍微简单的版本,它不跟踪其大小,以便我们可以更轻松地处理“无限”集(不是真正的无限,因为 Int 是有限的,但我们可以说“非常大且难以确定大小”)。
struct FunctionSet {
static let empty = Self(member: { _ in false })
let member: (Int) -> Bool
func insert(_ x: Int) -> Self {
member(x) ? self : Self(member: { x == $0 || member($0) })
}
}
现在我可以创建一些非常有趣的集合(并演示了为什么在这里实现
size
很烦人):
let evens = FunctionSet(member: { $0.isMultiple(of: 2)})
let evensAnd101 = evens.insert(101)
使用传统的 Swift Set 捕获“所有偶数和 101”是不切实际的。它会太大并且查找会非常慢。但 FunctionSet 非常高效地实现了这一点。
现在,这如何应用于面向协议的编程? POP 的要点是允许我们基于承诺的接口编写算法。看起来像这样:
protocol SetProviding {
static var empty: Self { get }
var member: (Int) -> Bool { get }
func insert(_: Int) -> Self
}
FunctionSet 确实做到了这一点,所以我们可以追溯地符合它,无需额外的工作:
extension FunctionSet: SetProviding {}
SetProviding 不要求用函数来实现。我们可以使用 Swift 内置的 Set 来实现它:
struct SetSet: SetProviding {
static var empty: Self { Self(values: []) }
private let storage: Set<Int>
// The double {{}} here is because member returns a function
var member: (Int) -> Bool { { storage.contains($0) } }
init(values: some Sequence<Int>) { storage = Set(values)}
func insert(_ x: Int) -> SetSet { Self(values: storage.union([x])) }
}
这样,我们就可以通过可以处理任何类型的 SetProviding 的
useSets
获得协议的强大功能和函数的强大功能:
func useSets(startingWith s1: some SetProviding) -> Int {
let s2 = s1.insert(34)
let s3 = s2.insert(34)
let s4 = s3.insert(19)
if s4.member(42) {
return 99
} else if s4.member(19) {
return 17
} else {
return 0
}
}
useSets(startingWith: SetSet.empty)
useSets(startingWith: evensAnd101)