diff --git a/README.md b/README.md index abe0ba80..47791f90 100644 --- a/README.md +++ b/README.md @@ -178,8 +178,6 @@ func sumsquare(a: T, b: T) -> Fallible { Now that you know the basics of error propagation, let's equip you with the means to take on the world. While `map(_:)` is fantastic, at times, you may have noticed that it sometimes devolves into a pyramid of doom. In other words, it doesn't scale to fit the needs of more complex problems. But do not worry, the `sink(_:)` method has arrived! It lets you offload error indicators between operations. Let's rewrite our example by using another formula. ```swift -// tip: Fallible.sink(_:) creates the Bool and calls veto(_:) - func sumsquare(a: T, b: T) -> Fallible { var w: Bool = false let x: T = a.squared().sink(&w) @@ -223,3 +221,17 @@ pair.value.append(", World") pair.error.toggle() let (value, error) = pair.components() ``` + +#### Conveniences: `error(...)`, `init(_:error:setup:)` + +With sufficient hands-on experience, you may notice a few recurring usage patterns. For example, the ever-so-useful `sink(_:)` method requires a mutable error indicator that you usually want to merge at the end. The static `error(...)` functions cover you on both fronts. At other times, you may want to consume an initial value. In that case, you should consider using `init(_:error:setup:)`. + +```swift +let x0 = Fallible.error { + U8.zero.decremented().sink(&$0) +} // value: 255, error: true + +let x1 = Fallible(U8.zero) { + $0 = $0.decremented().sink(&$1) +} // value: 255, error: true +``` diff --git a/Sources/CoreKit/Models/Fallible+Setup.swift b/Sources/CoreKit/Models/Fallible+Setup.swift new file mode 100644 index 00000000..d639f061 --- /dev/null +++ b/Sources/CoreKit/Models/Fallible+Setup.swift @@ -0,0 +1,83 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum open source project. +// +// Copyright (c) 2023 Oscar Byström Ericsson +// Licensed under Apache License, Version 2.0 +// +// See http://www.apache.org/licenses/LICENSE-2.0 for license information. +//=----------------------------------------------------------------------------= + +//*============================================================================* +// MARK: * Fallible x Setup +//*============================================================================* + +extension Fallible { + + //=------------------------------------------------------------------------= + // MARK: Initializers + //=------------------------------------------------------------------------= + + /// Creates a new instance by combining the mutable `value` and `error` + /// indicator passed to the `setup` function by consuming them when the + /// the `setup` function returns. + /// + /// ```swift + /// let x = Fallible(U8.zero) { value, error in + /// value = value.decremented().sink(&error) + /// } // value: 255, error: true + /// ``` + /// + /// - Note: The default `error` indicator is `false`. + /// + @inlinable public init( + _ + value: consuming Value, + error: consuming Bool = false, + setup: (inout Value, inout Bool) throws(Error) -> Void + ) throws(Error) { + + try setup(&value, &error) + self.init(value, error: error) + } + + //=------------------------------------------------------------------------= + // MARK: Initializers x Error + //=------------------------------------------------------------------------= + + /// Creates a new instance by combining the mutable `error` indicator passed + /// to the `setup` function and the `value` that the `setup` function returns. + /// + /// ```swift + /// let x = Fallible.error { error in + /// U8.zero.decremented().sink(&error) + /// } // value: 255, error: true + /// ``` + /// + /// - Note: The default `error` indicator is `false`. + /// + @inlinable public static func error( + _ setup: (inout Bool) throws(Error) -> Value + ) throws(Error) -> Self { + + try Self.error(false, setup: setup) + } + + /// Creates a new instance by combining the mutable `error` indicator passed + /// to the `setup` function and the `value` that the `setup` function returns. + /// + /// ```swift + /// let x = Fallible.error { error in + /// U8.zero.decremented().sink(&error) + /// } // value: 255, error: true + /// ``` + /// + /// - Note: The default `error` indicator is `false`. + /// + @inlinable public static func error( + _ error: consuming Bool, setup: (inout Bool) throws(Error) -> Value + ) throws(Error) -> Self { + + let value = try setup(&error) + return Self(value, error: error) + } +} diff --git a/Sources/CoreKit/Models/Fallible+Sink.swift b/Sources/CoreKit/Models/Fallible+Sink.swift index 306c3278..ccccb30a 100644 --- a/Sources/CoreKit/Models/Fallible+Sink.swift +++ b/Sources/CoreKit/Models/Fallible+Sink.swift @@ -13,24 +13,6 @@ extension Fallible { - //=------------------------------------------------------------------------= - // MARK: Initializers - //=------------------------------------------------------------------------= - - /// Generates an `error` indicator then combines it at the end of the `action`. - @inlinable public static func sink(_ action: (inout Bool) throws -> Self) rethrows -> Self { - var error = false - let value = try action(&error) - return (((value))).veto(error) - } - - /// Generates an `error` indicator then combines it at the end of the `action`. - @inlinable public static func sink(_ action: (inout Bool) throws -> Value) rethrows -> Self { - var error = false - let value = try action(&error) - return Self(value,error:error) - } - //=------------------------------------------------------------------------= // MARK: Transformations //=------------------------------------------------------------------------= diff --git a/Sources/CoreKit/Models/Fallible.swift b/Sources/CoreKit/Models/Fallible.swift index 3d4140d8..27565eec 100644 --- a/Sources/CoreKit/Models/Fallible.swift +++ b/Sources/CoreKit/Models/Fallible.swift @@ -41,11 +41,6 @@ // MARK: Initializers //=------------------------------------------------------------------------= - /// Creates an unset `error` indicator. - @inlinable public init() where Value == Void { - self.init(()) - } - /// Creates a new instance from the given `value` and `error`. @inlinable public init(_ value: consuming Value, error: consuming Bool = false) { self.value = value diff --git a/Tests/CoreKitTests/Fallible+Setup.swift b/Tests/CoreKitTests/Fallible+Setup.swift new file mode 100644 index 00000000..3399dacd --- /dev/null +++ b/Tests/CoreKitTests/Fallible+Setup.swift @@ -0,0 +1,70 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum open source project. +// +// Copyright (c) 2023 Oscar Byström Ericsson +// Licensed under Apache License, Version 2.0 +// +// See http://www.apache.org/licenses/LICENSE-2.0 for license information. +//=----------------------------------------------------------------------------= + +import CoreKit +import TestKit + +//*============================================================================* +// MARK: * Fallible x Setup +//*============================================================================* + +@Suite struct FallibleTestsOnSetup { + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + @Test( + "Fallible/setup: Self.init(_:setup:)", + Tag.List.tags(.exhaustive), + arguments: Bit.all, Bool.all + ) func fromInoutValueAndInoutError(value: Bit, error: Bool) throws { + #expect(Fallible(value, setup: { _, _ in }) == Fallible(value, error: false)) + #expect(Fallible(value, setup: { $0 = Bit.zero; $1 = false }) == Fallible(Bit.zero, error: false)) + #expect(Fallible(value, setup: { $0 = Bit.zero; $1 = true }) == Fallible(Bit.zero, error: true )) + #expect(Fallible(value, setup: { $0 = Bit.one; $1 = false }) == Fallible(Bit.one, error: false)) + #expect(Fallible(value, setup: { $0 = Bit.one; $1 = true }) == Fallible(Bit.one, error: true )) + } + + @Test( + "Fallible/setup: Self.init(_:error:setup:)", + Tag.List.tags(.exhaustive), + arguments: Bit.all, Bool.all + ) func fromInoutValueAndInoutErrorWithCustomInitialError(value: Bit, error: Bool) throws { + #expect(Fallible(value, error: error, setup: { _, _ in }) == Fallible(value, error: error)) + #expect(Fallible(value, error: error, setup: { $0 = Bit.zero; $1 = false }) == Fallible(Bit.zero, error: false)) + #expect(Fallible(value, error: error, setup: { $0 = Bit.zero; $1 = true }) == Fallible(Bit.zero, error: true )) + #expect(Fallible(value, error: error, setup: { $0 = Bit.one; $1 = false }) == Fallible(Bit.one, error: false)) + #expect(Fallible(value, error: error, setup: { $0 = Bit.one; $1 = true }) == Fallible(Bit.one, error: true )) + } + + //=------------------------------------------------------------------------= + // MARK: Tests x Error + //=------------------------------------------------------------------------= + + @Test( + "Fallible/setup: Self.error(_:)", + Tag.List.tags(.exhaustive), + arguments: Bit.all + ) func fromInoutErrorReturningValue(value: Bit) throws { + #expect(Fallible.error({ _ in return value }) == Fallible(value, error: false)) + #expect(Fallible.error({ $0 = false; return value }) == Fallible(value, error: false)) + #expect(Fallible.error({ $0 = true; return value }) == Fallible(value, error: true )) + } + + @Test( + "Fallible/setup: Self.error(_:setup:)", + Tag.List.tags(.exhaustive), + arguments: Bit.all, Bool.all + ) func fromInoutErrorReturningValueWithCustomInitialError(value: Bit, error: Bool) throws { + #expect(Fallible.error(error, setup: { _ in return value }) == Fallible(value, error: error)) + #expect(Fallible.error(error, setup: { $0 = false; return value }) == Fallible(value, error: false)) + #expect(Fallible.error(error, setup: { $0 = true; return value }) == Fallible(value, error: true )) + } +} diff --git a/Tests/CoreKitTests/Fallible+Sink.swift b/Tests/CoreKitTests/Fallible+Sink.swift index 698d3178..c05bf1bf 100644 --- a/Tests/CoreKitTests/Fallible+Sink.swift +++ b/Tests/CoreKitTests/Fallible+Sink.swift @@ -21,34 +21,6 @@ import TestKit // MARK: Tests //=------------------------------------------------------------------------= - @Test( - "Fallible/sink: from inout Bool as Bit", - Tag.List.tags(.exhaustive), - arguments: Bit.all - ) func fromInoutBoolAsBit(value: Bit) { - - #expect(Fallible.sink({ _ in return value }).value == value) - #expect(Fallible.sink({ _ in return value }).error == false) - #expect(Fallible.sink({ $0 = false; return value }).value == value) - #expect(Fallible.sink({ $0 = false; return value }).error == false) - #expect(Fallible.sink({ $0 = true; return value }).value == value) - #expect(Fallible.sink({ $0 = true; return value }).error) - } - - @Test( - "Fallible/sink: from inout Bool as Fallible", - Tag.List.tags(.exhaustive), - arguments: Fallible.all - ) func fromInoutBoolAsFallibleBit(instance: Fallible) { - - #expect(Fallible.sink({ _ in return instance }).value == instance.value) - #expect(Fallible.sink({ _ in return instance }).error == instance.error) - #expect(Fallible.sink({ $0 = false; return instance }).value == instance.value) - #expect(Fallible.sink({ $0 = false; return instance }).error == instance.error) - #expect(Fallible.sink({ $0 = true; return instance }).value == instance.value) - #expect(Fallible.sink({ $0 = true; return instance }).error) - } - @Test( "Fallible/sink: into Bool", Tag.List.tags(.exhaustive), diff --git a/Tests/CoreKitTests/Fallible.swift b/Tests/CoreKitTests/Fallible.swift index 9b109a73..79baf2e5 100644 --- a/Tests/CoreKitTests/Fallible.swift +++ b/Tests/CoreKitTests/Fallible.swift @@ -17,42 +17,80 @@ import TestKit @Suite struct FallibleTests { //=------------------------------------------------------------------------= - // MARK: Tests x Metadata + // MARK: Tests //=------------------------------------------------------------------------= @Test( - "Fallible: Optional has same memory layout as Self", + "Fallible: Optional has same memory layout", Tag.List.tags(.documentation, .important) - ) func optionalInstanceHasSameMemoryLayoutAsInstance() { + ) func optionalInstanceHasSameMemoryLayout() { whereIs(Bool.self) whereIs(Void.self) for type in typesAsCoreInteger { - whereIsBinaryInteger(type) + whereIs(type) } func whereIs(_ type: T.Type) { - Ɣexpect(MemoryLayout>>.self, equals: MemoryLayout>.self) - } - - func whereIsBinaryInteger(_ type: T.Type) where T: BinaryInteger { - Ɣexpect(MemoryLayout>>.self, equals: MemoryLayout>.self) + typealias A = MemoryLayout> + typealias B = MemoryLayout>> + Ɣexpect(A.self, equals: B.self) } } //=------------------------------------------------------------------------= - // MARK: Initializers + // MARK: Tests //=------------------------------------------------------------------------= @Test( - "Fallible: init() where Value is Void", - Tag.List.tags(.disambiguation, .exhaustive) - ) func initAsVoid() { + "Fallible: Self.init(_:)", + Tag.List.tags(.exhaustive), + arguments: Bit.all + ) func fromValue(value: Bit) { + #expect(Fallible(value).value == value) + #expect(Fallible(value).error == false) + } + + @Test( + "Fallible: Self.init(_:error:)", + Tag.List.tags(.exhaustive), + arguments: Bit.all, Bool.all + ) func fromValueWithError(value: Bit, error: Bool) { + #expect(Fallible(value, error: error).value == value) + #expect(Fallible(value, error: error).error == error) + } + + @Test( + "Fallible: Self.init(raw:)", + Tag.List.tags(.exhaustive), + arguments: Array<(Fallible, Fallible)>.infer([ + + (Fallible(Bit.zero, error: false), Fallible(Sign.plus, error: false)), + (Fallible(Bit.zero, error: true ), Fallible(Sign.plus, error: true )), + (Fallible(Bit.one, error: false), Fallible(Sign.minus, error: false)), + (Fallible(Bit.one, error: true ), Fallible(Sign.minus, error: true )), + + ])) func bitcasting(source: Fallible, destination: Fallible) { + #expect(Fallible(raw: source) == destination) + #expect(Fallible(raw: destination) == source) + } + + @Test( + "Fallible: Self/components()", + Tag.List.tags(.exhaustive) + ) func accessors() { + var instance = Fallible(Bit.zero, error: false) + + #expect( instance.value.isZero) + #expect(!instance.error) + #expect( instance.components() == (Bit.zero, false)) + + instance.value.toggle() + instance.error.toggle() - #expect(Fallible.init ().value == ()) - #expect(Fallible.init ().error == false) - #expect(Fallible().value == ()) - #expect(Fallible().error == false) + #expect(!instance.value.isZero) + #expect( instance.error) + #expect( instance.components() == (Bit.one, true )) } } diff --git a/Tests/UltimathnumTests/BinaryInteger+Multiplication.swift b/Tests/UltimathnumTests/BinaryInteger+Multiplication.swift index 6834f667..6a0121d5 100644 --- a/Tests/UltimathnumTests/BinaryInteger+Multiplication.swift +++ b/Tests/UltimathnumTests/BinaryInteger+Multiplication.swift @@ -181,21 +181,23 @@ import TestKit let c = T.entropic(size: size, using: &randomness) let d = T.entropic(size: size, using: &randomness) - let x = Fallible.sink { - let e: T = a.plus(b).sink(&$0) - let f: T = c.plus(d).sink(&$0) - return e.times(f) + let x = Fallible.error { + let e: T = a.plus( b).sink(&$0) + let f: T = c.plus( d).sink(&$0) + return e.times(f).sink(&$0) } - let y = Fallible.sink { + let y = Fallible.error { let e: T = a.times(c).sink(&$0) let f: T = a.times(d).sink(&$0) let g: T = b.times(c).sink(&$0) let h: T = b.times(d).sink(&$0) - return e.plus(f).sink(&$0).plus(g).sink(&$0).plus(h) + return e.plus(f).sink(&$0).plus(g).sink(&$0).plus(h).sink(&$0) + } + + always: do { + try #require(x.value == y.value) } - - try #require(x.value == y.value) if !T.isSigned, !x.value.isZero { try #require(x.error == y.error) @@ -228,18 +230,20 @@ import TestKit let a = T.entropic(size: size, using: &randomness) let b = T.entropic(size: size, using: &randomness) - let x = Fallible.sink { - a.plus(b).sink(&$0).squared() + let x = Fallible.error { + a.plus(b).sink(&$0).squared().sink(&$0) } - let y = Fallible.sink { + let y = Fallible.error { let c: T = a.squared().sink(&$0) - let d: T = a.times(b ).sink(&$0).times(2).sink(&$0) + let d: T = a.times(b ).sink(&$0).doubled().sink(&$0) let e: T = b.squared().sink(&$0) - return c.plus(d).sink(&$0).plus(e) + return c.plus(d).sink(&$0).plus(e).sink(&$0) } - try #require(x.value == y.value) + always: do { + try #require(x.value == y.value) + } if a.isNegative == b.isNegative { try #require(x.error == y.error) diff --git a/Tests/UltimathnumTests/Divider.swift b/Tests/UltimathnumTests/Divider.swift index b2b5f74f..9e320399 100644 --- a/Tests/UltimathnumTests/Divider.swift +++ b/Tests/UltimathnumTests/Divider.swift @@ -107,7 +107,7 @@ import TestKit //*============================================================================* @Suite struct DividerTests21 { - + //=------------------------------------------------------------------------= // MARK: Tests //=------------------------------------------------------------------------=