Skip to content

Commit c837d87

Browse files
OguzYuukselOguz Yuksel
andauthored
New free function mutate (#40)
* add mutate free function. * Update FPUtils.swift rename `module` to `root` * fix `KeyPath` * Update mutations for multiple values. * add documentation and tests * add `nilOutIfEmpty` property for `String` and `Optional<String>`. --------- Co-authored-by: Oguz Yuksel <oguz.yueksel@teufel.de>
1 parent 6bf3ae7 commit c837d87

File tree

6 files changed

+169
-0
lines changed

6 files changed

+169
-0
lines changed

Sources/FoundationExtensions/Collection/String+Extensions.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,10 @@ extension String {
1818
}
1919
}
2020
}
21+
22+
extension String {
23+
/// - Returns: `nil` if `isEmpty` is `true`, else returns `String`.
24+
public var nilOutIfEmpty: Self? {
25+
isEmpty ? nil : self
26+
}
27+
}

Sources/FoundationExtensions/Function/FPUtils.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,54 @@ public func setter<Root, Value>(_ keyPath: WritableKeyPath<Root, Value>) -> (ino
5858
root[keyPath: keyPath] = value
5959
}
6060
}
61+
62+
/// `mutate(root:, with:)` is useful when you want to derive a value from an existing value instead of initializing from scratch.
63+
/// It is basically a shortened & nicer syntax of a closure.
64+
///
65+
/// // Closure version
66+
/// { root in
67+
/// var mutableRoot = root
68+
/// mutableRoot.value1 = ""
69+
/// mutableRoot.value2 = 0
70+
/// mutableRoot.value3 = false
71+
/// return mutableRoot
72+
/// }(root)
73+
///
74+
/// // `mutate(root:, with:)` version
75+
/// mutate(root: root) {
76+
/// mutableRoot.value1 = ""
77+
/// mutableRoot.value2 = 0
78+
/// mutableRoot.value3 = false
79+
/// }
80+
///
81+
/// - Parameters:
82+
/// - root: Root value for derivation.
83+
/// - mutation: Closure where the derivation is applied.
84+
/// - Returns: Derived version of root.
85+
public func mutate<Root>(root: Root, with mutation: @escaping (inout Root) -> ()) -> Root {
86+
var root = root
87+
mutation(&root)
88+
return root
89+
}
90+
91+
/// `mutate(root:, with:, for:)` is useful when you want to derive a value from an existing value instead of initializing from scratch.
92+
/// It is basically a shortened & nicer syntax of a closure.
93+
///
94+
/// // Closure version
95+
/// { root in
96+
/// var mutableRoot = root
97+
/// mutableRoot.value1 = "New!"
98+
/// return mutableRoot
99+
/// }(root)
100+
///
101+
/// // `mutate(root:, with:)` version
102+
/// mutate(root: root, with: "New!", for: \.value1)
103+
///
104+
/// - Parameters:
105+
/// - root: Root value for derivation.
106+
/// - value: Value to be changed in the root.
107+
/// - for: Keypath for the value in the root.
108+
/// - Returns: Derived version of root.
109+
public func mutate<Root, Value>(root: Root, with value: Value, for: WritableKeyPath<Root, Value>) -> Root {
110+
mutate(root: root) { $0[keyPath: `for`] = value }
111+
}

Sources/FoundationExtensions/Optional/Optional+Extensions.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ extension Optional where Wrapped: Collection {
1414
}
1515
}
1616

17+
extension Optional where Wrapped == String {
18+
/// - Returns: `nil` if `isEmpty` is `true` or value is `nil`, else returns `String`.
19+
public var nilOutIfEmpty: Self {
20+
flatMap { $0.nilOutIfEmpty }
21+
}
22+
}
23+
1724
extension Optional {
1825
typealias Generic<A> = A?
1926

Tests/FoundationExtensionsTests/FunctionalExtensionsTests.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,49 @@ class FunctionalExtensionsTests: XCTestCase {
6363
// then
6464
XCTAssertEqual(expectedArrayCompacted, arrayCompacted)
6565
}
66+
67+
func testMutateForMultipleProperties() {
68+
// given
69+
struct MutableValue: Equatable {
70+
var value1: String = ""
71+
var value2: Int = 0
72+
var value3: Date = .init(timeIntervalSince1970: 100)
73+
}
74+
let immutableValue: MutableValue = .init()
75+
let expectedValue: MutableValue = .init(value1: "TestResult",
76+
value2: 999,
77+
value3: .init(timeIntervalSince1970: 9999))
78+
79+
// when
80+
let mutatedVersionOfValue = mutate(root: immutableValue) {
81+
$0.value1 = "TestResult"
82+
$0.value2 = 999
83+
$0.value3 = .init(timeIntervalSince1970: 9999)
84+
}
85+
86+
// then
87+
XCTAssertEqual(expectedValue, mutatedVersionOfValue)
88+
}
89+
90+
func testMutateForOneProperty() {
91+
// given
92+
struct MutableValue: Equatable {
93+
var value1: String = ""
94+
var value2: Int = 0
95+
var value3: Date = .init(timeIntervalSince1970: 100)
96+
}
97+
let immutableValue: MutableValue = .init()
98+
let expectedValue: MutableValue = .init(value1: "",
99+
value2: 15,
100+
value3: .init(timeIntervalSince1970: 100))
101+
102+
// when
103+
let mutatedVersionOfValue = mutate(root: immutableValue,
104+
with: 15,
105+
for: \.value2)
106+
107+
// then
108+
XCTAssertEqual(expectedValue, mutatedVersionOfValue)
109+
}
66110
}
67111
#endif

Tests/FoundationExtensionsTests/OptionalExtensionTests.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,5 +240,41 @@ extension OptionalExtensionTests {
240240
// then
241241
XCTAssertEqual(expectedResult, sut)
242242
}
243+
244+
func testNilOutIfEmptyWhenStringIsEmptyReturnsNil() {
245+
// given
246+
let optional: String? = ""
247+
let expectedResult: String? = nil
248+
249+
// when
250+
let sut = optional?.nilOutIfEmpty
251+
252+
// then
253+
XCTAssertEqual(expectedResult, sut)
254+
}
255+
256+
func testNilOutIfEmptyWhenStringIsNotEmptyReturnsString() {
257+
// given
258+
let optional: String? = "abc"
259+
let expectedResult: String? = "abc"
260+
261+
// when
262+
let sut = optional?.nilOutIfEmpty
263+
264+
// then
265+
XCTAssertEqual(expectedResult, sut)
266+
}
267+
268+
func testNilOutIfEmptyWhenStringIsNilReturnsNil() {
269+
// given
270+
let optional: String? = nil
271+
let expectedResult: String? = nil
272+
273+
// when
274+
let sut = optional?.nilOutIfEmpty
275+
276+
// then
277+
XCTAssertEqual(expectedResult, sut)
278+
}
243279
}
244280
#endif

Tests/FoundationExtensionsTests/StringExtensionsTest.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,29 @@ class StringExtensionsTest: XCTestCase {
1616
XCTAssertEqual("a", "a".leftPadding(toLength: 1, withPad: "0")) // no padding
1717
XCTAssertEqual("acab", "acab".leftPadding(toLength: 3, withPad: "0")) // no truncation
1818
}
19+
20+
func testNilOutIfEmptyWhenStringIsEmptyReturnsNil() {
21+
// given
22+
let value: String = ""
23+
let expectedResult: String? = nil
24+
25+
// when
26+
let sut = value.nilOutIfEmpty
27+
28+
// then
29+
XCTAssertEqual(expectedResult, sut)
30+
}
31+
32+
func testNilOutIfEmptyWhenStringIsNotEmptyReturnsString() {
33+
// given
34+
let value: String = "abc"
35+
let expectedResult: String? = "abc"
36+
37+
// when
38+
let sut = value.nilOutIfEmpty
39+
40+
// then
41+
XCTAssertEqual(expectedResult, sut)
42+
}
1943
}
2044
#endif

0 commit comments

Comments
 (0)