如何为 SwiftData @Query 创建“ENDSWITH”谓词?

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

我可以很好地创建各种谓词,但我不确定我可以使用什么来实现“ENDSWITH”?

extension Filter {

    var predicate: Predicate<WordModel>? {
        switch self {
        case .all:
            .none
        case .lettersContaining(let string):
            #Predicate<WordModel> {
                $0.letters.localizedStandardContains(string)
            }
        case .lettersStartingWith(let string):
            #Predicate<WordModel> {
                $0.letters.starts(with: string)
            }
        case .lettersEndingWith(let string):
            #Predicate<WordModel> {
                $0.letters.hasSuffix(string) // 🛑
            }
        }
    }
}

此代码表示“此谓词不支持 hasSuffix(_:) 函数”。

我可以在文档中看到(在PredicateExpressions)谓词闭包是一个构建器表达式,可以包含一组固定的表达式,我只是不确定在我的用例中使用什么。

您使用什么来为 SwiftData 创建“ENDSWITH”谓词

@Query

swift predicate swift-data swift-data-modelcontext
1个回答
0
投票

在撰写本文时,这绝对不存在。您可以在

#Predicate
宏支持的所有功能列表这里

private var _knownSupportedFunctions: Set<FunctionStructure> = [
    FunctionStructure("contains", arguments: [.unlabeled]),
    FunctionStructure("contains", arguments: [.closure(labeled: "where")]),
    FunctionStructure("allSatisfy", arguments: [.closure(labeled: nil)]),
    FunctionStructure("flatMap", arguments: [.closure(labeled: nil)]),
    FunctionStructure("filter", arguments: [.closure(labeled: nil)]),
    FunctionStructure("subscript", arguments: [.unlabeled]),
    FunctionStructure("subscript", arguments: [.unlabeled, "default"]),
    FunctionStructure("starts", arguments: ["with"]),
    FunctionStructure("min", arguments: []),
    FunctionStructure("max", arguments: []),
    FunctionStructure("localizedStandardContains", arguments: [.unlabeled]),
    FunctionStructure("localizedCompare", arguments: [.unlabeled]),
    FunctionStructure("caseInsensitiveCompare", arguments: [.unlabeled])
]

可以实现此行为,但您将无法使用

#Predicate
宏来使用它,而且它不会很漂亮。

第 1 步 - 实现
ends(with:)
函数

令我惊讶的是,没有

ends(with:)
starts(with:)
对应。

我们可以实现一个:

extension BidirectionalCollection where Element: Equatable {
  // Adapted from https://github.com/apple/swift/blob/d6f940/stdlib/public/core/SequenceAlgorithms.swift#L281-L286
  @inlinable
  public func ends<PossibleSuffix: BidirectionalCollection>(
    with possibleSuffix: PossibleSuffix
  ) -> Bool where PossibleSuffix.Element == Element {
    return self.ends(with: possibleSuffix, by: ==)
  }

  // Adapted from https://github.com/apple/swift/blob/d6f940/stdlib/public/core/SequenceAlgorithms.swift#L235-L252
  @inlinable
  public func ends<PossibleSuffix: BidirectionalCollection>(
    with possibleSuffix: PossibleSuffix,
    by areEquivalent: (Element, PossibleSuffix.Element) throws -> Bool
  ) rethrows -> Bool {
    var possibleSuffixIterator = possibleSuffix.reversed().makeIterator()
    for e0 in self.reversed() {
      if let e1 = possibleSuffixIterator.next() {
        if try !areEquivalent(e0, e1) {
          return false
        }
      }
      else {
        return true
      }
    }
    return possibleSuffixIterator.next() == nil
  }
}

第 2 步 - 创建新的
PredicateExpression

接下来,我们需要添加包装它所需的各种谓词类型。我通过复制

SequenceStartsWith
相关代码并根据需要进行调整来编写所有这些。


extension PredicateExpressions {
  // Adapted from `SequenceStartsWith`
  // https://github.com/apple/swift-foundation/blob/c4fa86/Sources/FoundationEssentials/Predicate/Expressions/Sequence.swift#L101
  public struct CollectionEndsWith<
  Base : PredicateExpression,
  Suffix : PredicateExpression
  > : PredicateExpression
  where
  Base.Output : BidirectionalCollection,
  Suffix.Output : BidirectionalCollection,
  Base.Output.Element == Suffix.Output.Element,
  Base.Output.Element : Equatable
  {
    public typealias Output = Bool
    
    public let base: Base
    public let suffix: Suffix
    
    public init(base: Base, suffix: Suffix) {
      self.base = base
      self.suffix = suffix
    }
    
    public func evaluate(_ bindings: PredicateBindings) throws -> Bool {
      try base.evaluate(bindings).ends(with: try suffix.evaluate(bindings))
    }
  }
}

// Adapted from https://github.com/apple/swift-foundation/blob/c4fa86/Sources/FoundationEssentials/Predicate/Expressions/Sequence.swift#L226-L239
@available(FoundationPredicate 0.1, *)
extension PredicateExpressions.CollectionEndsWith : Codable where Base : Codable, Suffix : Codable {
  public func encode(to encoder: Encoder) throws {
    var container = encoder.unkeyedContainer()
    try container.encode(base)
    try container.encode(suffix)
  }
  
  public init(from decoder: Decoder) throws {
    var container = try decoder.unkeyedContainer()
    self.base = try container.decode(Base.self)
    self.suffix = try container.decode(Suffix.self)
  }
}

// Adapted from https://github.com/apple/swift-foundation/blob/c4fa86/Sources/FoundationEssentials/Predicate/Expressions/Sequence.swift#L174-L175
@available(FoundationPredicate 0.1, *)
extension PredicateExpressions.CollectionEndsWith : StandardPredicateExpression where Base : StandardPredicateExpression, Suffix : StandardPredicateExpression {}

extension PredicateExpressions {
  // Adapted from `build_starts`
  // https://github.com/apple/swift-foundation/blob/c4fa86/Sources/FoundationEssentials/Predicate/Expressions/Sequence.swift#L139
  public static func build_ends<Base, Suffix>(_ base: Base, with suffix: Suffix) -> CollectionEndsWith<Base, Suffix> {
    CollectionEndsWith(base: base, suffix: suffix)
  }
}

第 3 步 - 使用我们的新表达式

最后,我们需要实际使用这个新谓词。不幸的是,我们没有办法修改/扩展

#Predicate
宏语法,除非分叉它。

我们可以做的是,使用

#Predicate
starts(with:)
调用来启动,例如

let people = [
  "Alice",
  "Alex",
  "Bob",
  "Charlie",
  "Daniel",
].map(Person.init)

let predicate = #Predicate<Person> { person in
  person.name.starts(with: "A")
}

然后我们可以使用

swift -Xfrontend -dump-macro-expansions predicate_demo.swift
转储该宏的扩展:

Macro expands to:
let predicate = Foundation.Predicate<Person>({ person in
  PredicateExpressions.build_starts(
    PredicateExpressions.build_KeyPath(
      root: PredicateExpressions.build_Arg(person),
      keyPath: \.name
    ),
    with: PredicateExpressions.build_Arg("A")
  )
})

然后我们可以将

build_starts
修改为
build_ends
,以获得最终的示例:

let predicate = Foundation.Predicate<Person>({ person in
  PredicateExpressions.build_ends( // Replaced `.build_starts(`
    PredicateExpressions.build_KeyPath(
      root: PredicateExpressions.build_Arg(person),
      keyPath: \.name
    ),
    with: PredicateExpressions.build_Arg("e")
  )
})

let namesEndingWithE = try people.filter(predicate).map(\.name)
print(namesEndingWithE) // ["Alice", "Charlie"]
© www.soinside.com 2019 - 2024. All rights reserved.