如何重构结构体以在 Swift 中使用协议和闭包(学习目的)

问题描述 投票:0回答:1

我想改变的主要只是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内的字典(或如何实现它)

swift functional-programming
1个回答
0
投票

我相信我知道您想要实现的 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)
© www.soinside.com 2019 - 2024. All rights reserved.