Skip to content

Commit

Permalink
Rework some Fallible<T> iniitializers (#125) (#128).
Browse files Browse the repository at this point in the history
  • Loading branch information
oscbyspro committed Dec 2, 2024
1 parent 0d70290 commit bb7dbb5
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 85 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,6 @@ func sumsquare<T: UnsignedInteger>(a: T, b: T) -> Fallible<T> {
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<T: UnsignedInteger>(a: T, b: T) -> Fallible<T> {
var w: Bool = false
let x: T = a.squared().sink(&w)
Expand Down
81 changes: 81 additions & 0 deletions Sources/CoreKit/Models/Fallible+Setup.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//=----------------------------------------------------------------------------=
// 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
/// ```
///
@inlinable public init<Error>(
_
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: You may set the initial `error` indicator using one of the overloads.
///
@inlinable public static func error<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: You may set the initial `error` indicator using one of the overloads.
///
@inlinable public static func error<Error>(
_ error: consuming Bool = false, setup: (inout Bool) throws(Error) -> Value
) throws(Error) -> Self {

let value = try setup(&error)
return Self(value, error: error)
}
}
18 changes: 0 additions & 18 deletions Sources/CoreKit/Models/Fallible+Sink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
//=------------------------------------------------------------------------=
Expand Down
5 changes: 0 additions & 5 deletions Sources/CoreKit/Models/Fallible.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
70 changes: 70 additions & 0 deletions Tests/CoreKitTests/Fallible+Setup.swift
Original file line number Diff line number Diff line change
@@ -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 ))
}
}
28 changes: 0 additions & 28 deletions Tests/CoreKitTests/Fallible+Sink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Bit>.sink({ _ in return value }).value == value)
#expect(Fallible<Bit>.sink({ _ in return value }).error == false)
#expect(Fallible<Bit>.sink({ $0 = false; return value }).value == value)
#expect(Fallible<Bit>.sink({ $0 = false; return value }).error == false)
#expect(Fallible<Bit>.sink({ $0 = true; return value }).value == value)
#expect(Fallible<Bit>.sink({ $0 = true; return value }).error)
}

@Test(
"Fallible/sink: from inout Bool as Fallible<Bit>",
Tag.List.tags(.exhaustive),
arguments: Fallible<Bit>.all
) func fromInoutBoolAsFallibleBit(instance: Fallible<Bit>) {

#expect(Fallible<Bit>.sink({ _ in return instance }).value == instance.value)
#expect(Fallible<Bit>.sink({ _ in return instance }).error == instance.error)
#expect(Fallible<Bit>.sink({ $0 = false; return instance }).value == instance.value)
#expect(Fallible<Bit>.sink({ $0 = false; return instance }).error == instance.error)
#expect(Fallible<Bit>.sink({ $0 = true; return instance }).value == instance.value)
#expect(Fallible<Bit>.sink({ $0 = true; return instance }).error)
}

@Test(
"Fallible/sink: into Bool",
Tag.List.tags(.exhaustive),
Expand Down
72 changes: 55 additions & 17 deletions Tests/CoreKitTests/Fallible.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,42 +17,80 @@ import TestKit
@Suite struct FallibleTests {

//=------------------------------------------------------------------------=
// MARK: Tests x Metadata
// MARK: Tests
//=------------------------------------------------------------------------=

@Test(
"Fallible: Optional<Self> has same memory layout as Self",
"Fallible: Optional<Self> 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<T>(_ type: T.Type) {
Ɣexpect(MemoryLayout<Optional<Fallible<T>>>.self, equals: MemoryLayout<Fallible<T>>.self)
}

func whereIsBinaryInteger<T>(_ type: T.Type) where T: BinaryInteger {
Ɣexpect(MemoryLayout<Optional<Fallible<T>>>.self, equals: MemoryLayout<Fallible<T>>.self)
typealias A = MemoryLayout<Fallible<T>>
typealias B = MemoryLayout<Optional<Fallible<T>>>
Ɣ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<Bit>, Fallible<Sign>)>.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<Bit>, destination: Fallible<Sign>) {
#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<Void>().value == ())
#expect(Fallible<Void>().error == false)
#expect(!instance.value.isZero)
#expect( instance.error)
#expect( instance.components() == (Bit.one, true ))
}
}
32 changes: 18 additions & 14 deletions Tests/UltimathnumTests/BinaryInteger+Multiplication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>.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<T>.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)
Expand Down Expand Up @@ -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<T>.sink {
a.plus(b).sink(&$0).squared()
let x = Fallible.error {
a.plus(b).sink(&$0).squared().sink(&$0)
}

let y = Fallible<T>.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)
Expand Down
2 changes: 1 addition & 1 deletion Tests/UltimathnumTests/Divider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ import TestKit
//*============================================================================*

@Suite struct DividerTests21 {

//=------------------------------------------------------------------------=
// MARK: Tests
//=------------------------------------------------------------------------=
Expand Down

0 comments on commit bb7dbb5

Please sign in to comment.