Skip to content

Instantly share code, notes, and snippets.

@bradleymackey
Last active December 13, 2021 10:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bradleymackey/5d7ace2b4e6f5ee9b7fcc6f351b515f5 to your computer and use it in GitHub Desktop.
Save bradleymackey/5d7ace2b4e6f5ee9b7fcc6f351b515f5 to your computer and use it in GitHub Desktop.
import Foundation
/// Asynchrously supply and then remember a result.
public final class Eventual<T>: Sendable where T: Sendable {
/// Mediated access to the supplying function.
private let supplierInternal: Atomic<@Sendable () async -> T>
/// The closure that provides the value.
///
/// This will be called implictly when you `get()` the value.
public var supplier: @Sendable () async -> T {
get {
supplierInternal.get { $0 }
}
set {
supplierInternal.modify {
$0 = newValue
invalidateLastValue()
}
}
}
/// The backing storage for the value, which is thread safe.
private let underlyingValue: Atomic<T?> = .init(initialValue: nil)
/// The last computed value by the supplier.
public var value: T? {
underlyingValue.get { $0 }
}
public init(supplier: @escaping @Sendable () async -> T) {
self.supplierInternal = .init(initialValue: supplier)
}
/// Get the value, either the cached version or compute and then cache.
public func get() async -> T {
if let value = value {
return value
} else {
let computed = await supplier()
underlyingValue.modify {
$0 = computed
}
return computed
}
}
/// Has the value ever been computed as of yet?
public var isResolved: Bool {
value != nil
}
/// Invalidate the last cached value, forcing a re-computation.
public func invalidateLastValue() {
underlyingValue.modify {
$0 = nil
}
}
}
// MARK: - API
public extension Eventual where T: OptionalType, T.Wrapped: Sendable {
/// Flatten an optional return value to a non-optional return value.
///
/// - note: This holds a reference to the previous eventual, due to the nature of the
/// deferred computation. Therefore, do not create a deep stack of these.
func flatten(fallback: @autoclosure @escaping () -> T.Wrapped) -> Eventual<T.Wrapped> {
Eventual<T.Wrapped> {
let rawResult = await self.get()
if let value = rawResult.value {
return value
} else {
return fallback()
}
}
}
}
public extension Eventual {
/// Create a pre-resolved `Eventual<T>`.
static func value(_ val: T) -> Eventual<T> {
let eventual = Eventual(supplier: { val })
eventual.underlyingValue.modify {
$0 = val
}
return eventual
}
/// - note: This holds a reference to the previous eventual, due to the nature of the
/// deferred computation. Therefore, do not create a deep stack of these.
func map<U>(body transform: @escaping @Sendable (T) -> U) -> Eventual<U> {
Eventual<U> {
let value = await self.get()
let transformedValue = transform(value)
return transformedValue
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment