├── .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 | --------------------------------------------------------------------------------