Skip to content

Commit

Permalink
Fallible<T> initialization mini-rework (#144).
Browse files Browse the repository at this point in the history
* Rework some Fallible<T> iniitializers (#125) (#128).

* Write section about new Fallible<T> initializers.

* Some documentation comments.

* Cleanup of README.md.
  • Loading branch information
oscbyspro authored Dec 2, 2024
1 parent 0d70290 commit d7dc318
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 85 deletions.
16 changes: 14 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 Expand Up @@ -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
```
83 changes: 83 additions & 0 deletions Sources/CoreKit/Models/Fallible+Setup.swift
Original file line number Diff line number Diff line change
@@ -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<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: The default `error` indicator is `false`.
///
@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: The default `error` indicator is `false`.
///
@inlinable public static func error<Error>(
_ error: consuming Bool, 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 ))
}
}
Loading

0 comments on commit d7dc318

Please sign in to comment.