Closed oscbyspro closed 1 month ago
You can statically query the number of elements that fit in the body of a binary integer type, so an unchecked count is fine. I considered adding clamping behavior, but I don't really want unnecessary runtime checks for dynamic inputs. You can write a clamping function using an unchecked function, but you can't do the opposite.
Note that the compiler is quite adept at folding inlinable constant arguments (#45).
Hm. I'm still not sold on it. I think I'll simply start by prototyping some of the randomness (#45) utilities I need. I'll then evaluate how useful this is.
One could also imagine an approach similar to doStuffIfAvailable(_:)
:
static func arbitrary(
uninitialized count: IX,
repeating appendix: Bit,
initializer delegate: (MutableDataInt<Element.Magnitude>.Body) -> Void
) -> Optional<Self>
In this case, you'd statically branch on T.size.isInfinite
beforehand:
static func random(...) -> Self {
if !Self.size.isInfinite {
// some systems integer code
} else {
return Self.arbitrary(...)!
}
}
The above might actually be preferred since it is isomorphic with the first approach:
static func uninitialized(
unchecked count: IX,
repeating appendix: Bit,
initializer delegate: (MutableDataInt<Element.Magnitude>.Body) -> Void
) -> Self {
if !Self.size.isInfinite {
// one might allocate it unsafely too...
var instance = Self(repeating: appendix)
body.withUnsafeMutableBinaryIntegerBody(...)
return instance
} else {
return Self.arbitrary(...)!
}
}
Additionally, the arbitrary case might as well be optional in case of allocation failure.
Another benefit of BinaryInteger.arbitrary(...) is that arbitrary integers support initialized prefixes, whereas systems integer don't. I could, therefore, require that the delegate returns an initialized prefix count at the end of its execution.
protocol BinaryInteger {
/// Creates a new instance by manually initializing memory, but only if
/// this is an arbitrary binary integer type and the given arguments are
/// valid.
///
/// - Parameter count: The number of uninitialized elements that will be
/// passed to the `delegate`. It must not be negative or exceed the entropy
/// limit.
///
/// - Parameter appendix: The bit that extends the bit pattern initialized
/// by the `delegate`. Its significance depends on the signedness of this
/// binary integer type.
///
/// - Parameter delegate: A process that manually initializes a prefix in
/// the buffer passed to it. It must return the initialized prefix length
/// at the end of its execution. Note that `Void` is automatically
/// reinterpreted as the given `count` by a convenient function overload.
///
@inlinable static func arbitrary(
uninitialized count: IX,
repeating appendix: Bit,
initializer delegate: (MutableDataInt<Element.Magnitude>.Body) -> IX
) -> Optional<Self>
}
Hm. The repeating: Bit
part should also default to Bit.zero
, like it does everywhere else.
BinaryInteger.init(load: DataInt) is great when you have contiguous elements to copy, but you also need a generic way of requesting uninitialized memory. The latter lets you generate contiguous elements and store them without temporary allocations. Efficient random number generation is an immediate use case (#45). I imagine something like the following: