Skip to content

Commit

Permalink
Open
Browse files Browse the repository at this point in the history
  • Loading branch information
alexito4 committed Jan 3, 2022
1 parent 9353695 commit 74f1164
Show file tree
Hide file tree
Showing 10 changed files with 722 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,4 @@ fastlane/test_output
# https://github.com/johnno1962/injectionforxcode

iOSInjectionProject/
.DS_Store
7 changes: 7 additions & 0 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 92 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/Flow.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Flow"
BuildableName = "Flow"
BlueprintName = "Flow"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FlowTests"
BuildableName = "FlowTests"
BlueprintName = "FlowTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FlowTests"
BuildableName = "FlowTests"
BlueprintName = "FlowTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Flow"
BuildableName = "Flow"
BlueprintName = "Flow"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
22 changes: 22 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// swift-tools-version:5.5
import PackageDescription

let package = Package(
name: "Flow",
products: [
.library(
name: "Flow",
targets: ["Flow"]
),
],
targets: [
.target(
name: "Flow",
dependencies: []
),
.testTarget(
name: "FlowTests",
dependencies: ["Flow"]
),
]
)
184 changes: 183 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,184 @@
# Flow
Extension methods for fluent syntax in Swift.

🌊 Let your code *flow*.

This library provides a bunch of extension methods for a better fluent syntax in Swift. This style is very useful for some operations that benefit from being able to be chained (composed) together.

## Functionality

- `.then` to configure reference and value types. Useful for configuration at the point of initialization.

- `.mutate` in place value types.
- `.let` to transform an object into another.
- `.do` to perform multiple actions with the same object.
- Free function variants, for when you prefer this syntax or don't want to conform to the protocol:
- `with` (similar to `.then`)
- `withLet` (similar to `.let`)
- `run` as an alternative to immediately executed closures.

## `.then`

Use `.then` to perform an object configuration inline. It applies statements in the closure to the object. It's very useful to set the properties of an object when defining it.

```swift
let label = UILabel().then {
$0.text = "Hello"
$0.textColor = .red
$0.font = .preferredFont(forTextStyle: .largeTitle)
$0.sizeToFit()
}

let size = CGSize().then {
$0.width = 20
}
```

> There are two overloads of this method provided. One that works on `AnyObject` (a.k.a. classes) and another that operates on `Any` (intended for value types). The compiler picks the correct one appropriately.
- In the closure you get a reference to `self` or an `inout` copy in case of value types.
- It returns the same reference to the object, or the mutated copy for value types.

Influences:

- [Then.with](https://github.com/devxoul/Then/blob/master/Sources/Then/Then.swift#L42)
- [Kotlin.apply](https://kotlinlang.org/docs/scope-functions.html#apply) and [Kotlin.also](https://kotlinlang.org/docs/scope-functions.html#also)

## `.mutate`

Mutates a value **in place**. It s like `.then` but applies to `self` instead of a new copy. The value needs to be defined as a `var`.

```swift
view.frame.mutate {
$0.origin.y = 200
$0.size.width = 300
}
```

- In the closure you get an `inout` reference to `self` .
- It returns nothing.

> This should be used only for value types. For reference types is recommended to use `.then`.
## `.let`

You can think of `.let` as a `map` operation but for all the types (not only for *Functors*). It lets you transform the object into an object of another type.

```swift
let dateString: String = Date().let {
let formatter = DateFormatter()
return formatter.string(from: $0)
}
```

It works especially well for type conversions based on initializers:

```swift
let number: Int? = "42".let(Int.init)
```

> Don't overuse this when you can use just plain dot syntax. You can use it to access a member of the object `Date().let { $0.timeIntervalSince1970 }` but that's just the same as `Date().timeIntervalSince1970`.
- You get a reference to `self` in the closure.
- It returns the object returned in the closure.

Influences:

- Swift's own `let` declaration.
- [Kotlin.let](https://kotlinlang.org/docs/scope-functions.html#let) and [Kotlin.run](https://kotlinlang.org/docs/scope-functions.html#run).

## `.do`

Use this method to perform multiple actions (side effects) with the same object. It helps to reduce the verbosity of typing the same name multiple times.

```swift
UserDefaults.standard.do {
$0.set(42, forKey: "number")
$0.set("hello", forKey: "string")
$0.set(true, forKey: "bool")
}
```

This behaves like other methods if you discard their return, but is preferred to use `do` to convey the intention better. It also lets you avoid writing the `return` on some occasions.

- You get a reference to `self` in the closure.
- It returns nothing.

Influences:

- [Then.do](https://github.com/devxoul/Then/blob/master/Sources/Then/Then.swift#L56)

## `.debug`

By default, it prints `self` to the console. This method is useful for debugging intermediate values of a chain of method calls.

```swift
let result = Object()
.then { ... }
.debug("prefix")
.let { ... }
.debug()
```

- You get a reference to `self` in the closure.
- It returns the same object without touching it.

## Free function `with`
Executes a closure with the object. This free function it's a substitute for `.then` when you can't use the method or if you prefer the free function style.

```swift
let label = with(UILabel()) {
$0.text = "Hello"
$0.textColor = .red
$0.font = .preferredFont(forTextStyle: .largeTitle)
$0.sizeToFit()
}
```

- You get a reference to an `inout` copy of `self` in the closure.
- It returns the returned object in the closure.

Influences:

- [Overture.with](https://github.com/pointfreeco/swift-overture#with-and-update)
- [Kotlin.with](https://kotlinlang.org/docs/scope-functions.html#with)
- Many other languages have a `with` or `using` function.

## Free function `withLet`
Variant of `with` that let's you return a different type. It's a free function alternative of `let`.

## Free function `run`
Executes a closure of statements, useful to be used when you need an expression. This is like making a closure and invoking immediately but sometimes is clearer to have a specific name for it.

```swift
let result = run { ... } // same as { ... }()
```

Influences:

- [Kotlin.run](https://kotlinlang.org/docs/scope-functions.html#run)


## Supported Types

Since Swift doesn't let us extend non-nominal types like `Any` we need to conform each type to the `Flowable` protocol.

The library provides out of the box conformances for a bunch of Standard Library, Foundation and UIKit types. See [Conformances.swift](/Sources/Flow/Conformances.swift) for the entire list.

You can conform any type yourself by just extending it:

```swift
extension YourType: Flowable {}
```

> Note that you can use the free function variants without the types conforming to the protocol.
## Influences

- [devxoul/Then](https://github.com/devxoul/Then)
- Functional style approaches (like [Overture](https://github.com/pointfreeco/swift-overture))
- Kotlin [Scope Functions](https://kotlinlang.org/docs/scope-functions.html). Note that Swift can't "rebind self" inside a closure, so most of Kotlin's scope functions are redundant.
- Other languages that have a similar `with` or `using` functions.

# Author

Alejandro Martinez | http://alejandromp.com | [@alexito4](https://twitter.com/alexito4)
29 changes: 29 additions & 0 deletions Sources/Flow/Conformances.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
extension Array: Flowable {}
extension Dictionary: Flowable {}
extension Set: Flowable {}
extension String: Flowable {}
extension Int: Flowable {}
extension Double: Flowable {}
extension Float: Flowable {}

// MARK: Foundation

import Foundation

extension NSObject: Flowable {}
extension Date: Flowable {}
#if !os(Linux)
extension CGPoint: Flowable {}
extension CGRect: Flowable {}
extension CGSize: Flowable {}
extension CGVector: Flowable {}
#endif

// MARK: UIKit

#if os(iOS) || os(tvOS)
import UIKit.UIGeometry
extension UIEdgeInsets: Flowable {}
extension UIOffset: Flowable {}
extension UIRectEdge: Flowable {}
#endif
Loading

0 comments on commit 74f1164

Please sign in to comment.