Last active
December 13, 2021 10:56
-
-
Save bradleymackey/5d7ace2b4e6f5ee9b7fcc6f351b515f5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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