├── .github
└── workflows
│ └── CommitChecks.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── Wrap
│ └── Wrap.swift
└── Tests
└── WrapTests
└── File.swift
/.github/workflows/CommitChecks.yml:
--------------------------------------------------------------------------------
1 | name: CommitChecks
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build-with-swiftpm:
7 | runs-on: macOS-13
8 |
9 | steps:
10 | - uses: maxim-lobanov/setup-xcode@v1.1
11 | with:
12 | xcode-version: "15.0"
13 | - uses: actions/checkout@v2
14 | - name: Build
15 | run: swift build -v
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/swift
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=swift
4 |
5 | ### Swift ###
6 | # Xcode
7 | #
8 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
9 |
10 | ## User settings
11 | xcuserdata/
12 |
13 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
14 | *.xcscmblueprint
15 | *.xccheckout
16 |
17 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
18 | build/
19 | DerivedData/
20 | *.moved-aside
21 | *.pbxuser
22 | !default.pbxuser
23 | *.mode1v3
24 | !default.mode1v3
25 | *.mode2v3
26 | !default.mode2v3
27 | *.perspectivev3
28 | !default.perspectivev3
29 |
30 | ## Obj-C/Swift specific
31 | *.hmap
32 |
33 | ## App packaging
34 | *.ipa
35 | *.dSYM.zip
36 | *.dSYM
37 |
38 | ## Playgrounds
39 | timeline.xctimeline
40 | playground.xcworkspace
41 |
42 | # Swift Package Manager
43 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
44 | # Packages/
45 | # Package.pins
46 | # Package.resolved
47 | # *.xcodeproj
48 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
49 | # hence it is not needed unless you have added a package configuration file to your project
50 | # .swiftpm
51 |
52 | .build/
53 |
54 | # CocoaPods
55 | # We recommend against adding the Pods directory to your .gitignore. However
56 | # you should judge for yourself, the pros and cons are mentioned at:
57 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
58 | # Pods/
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
64 | # Carthage/Checkouts
65 |
66 | Carthage/Build/
67 |
68 | # Accio dependency management
69 | Dependencies/
70 | .accio/
71 |
72 | # fastlane
73 | # It is recommended to not store the screenshots in the git repo.
74 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
75 | # For more information about the recommended setup visit:
76 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
77 |
78 | fastlane/report.xml
79 | fastlane/Preview.html
80 | fastlane/screenshots/**/*.png
81 | fastlane/test_output
82 |
83 | # Code Injection
84 | # After new code Injection tools there's a generated folder /iOSInjectionProject
85 | # https://github.com/johnno1962/injectionforxcode
86 |
87 | iOSInjectionProject/
88 |
89 | .DS_Store
90 | # End of https://www.toptal.com/developers/gitignore/api/swift
91 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2020 Hiroshi Kimura
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:6.0
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "Wrap",
6 | products: [
7 | .library(name: "Wrap", targets: ["Wrap"]),
8 | ],
9 | targets: [
10 | .target(name: "Wrap", dependencies: []),
11 | .testTarget(name: "WrapTests", dependencies: ["Wrap"])
12 | ],
13 | swiftLanguageModes: [.v6]
14 | )
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # value`&>`.do { ... }
2 |
3 | A tiny library that enables us to describe operations in method-chain.
4 |
5 | ## Usage
6 |
7 | ```swift
8 |
9 | let result: String = ""&>.map { Int($0) }&>.do { print($0 as Any) }
10 |
11 | let value: String? = ""&>.filter { !$0.isEmpty }
12 |
13 | let view: UIView = UIView()&>.do {
14 | $0.backgroundColor = .white
15 | }
16 | ```
17 |
18 | ```swift
19 | let someProperty = await fetch()&>.map { data in
20 | data.someProperty
21 | }
22 | ```
23 |
24 | ## Motivation
25 |
26 | Actually, I'm not addicted to getting a custom operator.
27 | However **a global function** or **operator** are the only way to add a new feature without customizing our own types.
28 |
29 | Another way is using something protocol and extending it like this.
30 | It can not be used in struct without adding that protocol.
31 |
32 | In fact, this structure looks very natural, because the all of method chaining in Swift standard libary come from an kind of monad.
33 | Like, `Optional`, `Array` and `Dictionary`.
34 |
35 | And this keeps Swift clean code base at the beginning.
36 | You have no methods at you declared a struct!
37 |
--------------------------------------------------------------------------------
/Sources/Wrap/Wrap.swift:
--------------------------------------------------------------------------------
1 |
2 | postfix operator &>
3 | infix operator <&
4 |
5 | public postfix func &> (argument: consuming T) -> _FlowDownBox {
6 | .init(consume argument)
7 | }
8 |
9 | public func <& (argument: inout T, modifier: (inout T) throws -> Void) rethrows {
10 | try modifier(&argument)
11 | }
12 |
13 | public struct _FlowDownBox: ~Copyable {
14 |
15 | public var value: Value
16 |
17 | public init(_ value: consuming Value) {
18 | self.value = consume value
19 | }
20 |
21 | }
22 |
23 | public func modify(_ value: inout Value, _ modifier: (inout Value) throws -> Void) rethrows {
24 | try modifier(&value)
25 | }
26 |
27 | extension _FlowDownBox {
28 |
29 | public consuming func map(_ transform: (consuming Value) throws -> U) rethrows -> U {
30 | try transform(value)
31 | }
32 |
33 | @discardableResult
34 | public consuming func `do`(_ applier: (consuming Value) throws -> Void) rethrows -> Value {
35 | try applier(value)
36 | return value
37 | }
38 |
39 | @discardableResult
40 | @_disfavoredOverload
41 | public consuming func `do`(_ applier: (consuming Value) throws -> Void) rethrows -> Value? {
42 | try applier(value)
43 | return value
44 | }
45 |
46 | public consuming func modify(_ modifier: (inout Value) throws -> Void) rethrows -> Value {
47 | try modifier(&value)
48 | return value
49 | }
50 |
51 | public consuming func filter(_ filter: (consuming Value) -> Bool) -> Value? {
52 | guard filter(value) else {
53 | return nil
54 | }
55 | return value
56 | }
57 |
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/Tests/WrapTests/File.swift:
--------------------------------------------------------------------------------
1 |
2 | import XCTest
3 | import Wrap
4 |
5 | final class WrapTests: XCTestCase {
6 |
7 | func testWrap() {
8 | let value = 42
9 | let wrapped = value&>
10 | .map { $0 + 1 }
11 | XCTAssertEqual(wrapped, 43)
12 | }
13 |
14 | func testModify() {
15 | var value = 42
16 | value <& { $0 += 1 }
17 | XCTAssertEqual(value, 43)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------