Skip to content

Commit

Permalink
Add Zip (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephencelis authored Aug 17, 2018
1 parent e3e6387 commit 1632b5d
Show file tree
Hide file tree
Showing 6 changed files with 1,321 additions and 3 deletions.
12 changes: 12 additions & 0 deletions Overture.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
/* End PBXAggregateTarget section */

/* Begin PBXBuildFile section */
DC5E715C21065C6900ED239B /* ZipOptional.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5E715B21065C6900ED239B /* ZipOptional.swift */; };
DCF8237021267FD4000A7F94 /* ZipSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF8236F21267FD4000A7F94 /* ZipSequence.swift */; };
DCF82372212684BE000A7F94 /* ZipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF82371212684BE000A7F94 /* ZipTests.swift */; };
OBJ_44 /* Chain.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* Chain.swift */; };
OBJ_45 /* Compose.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* Compose.swift */; };
OBJ_46 /* Concat.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* Concat.swift */; };
Expand Down Expand Up @@ -64,6 +67,9 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
DC5E715B21065C6900ED239B /* ZipOptional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZipOptional.swift; sourceTree = "<group>"; };
DCF8236F21267FD4000A7F94 /* ZipSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZipSequence.swift; sourceTree = "<group>"; };
DCF82371212684BE000A7F94 /* ZipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZipTests.swift; sourceTree = "<group>"; };
OBJ_11 /* Chain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Chain.swift; sourceTree = "<group>"; };
OBJ_12 /* Compose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Compose.swift; sourceTree = "<group>"; };
OBJ_13 /* Concat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Concat.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -126,6 +132,8 @@
OBJ_21 /* Setters.swift */,
OBJ_22 /* Uncurry.swift */,
OBJ_23 /* With.swift */,
DC5E715B21065C6900ED239B /* ZipOptional.swift */,
DCF8236F21267FD4000A7F94 /* ZipSequence.swift */,
OBJ_24 /* Zurry.swift */,
);
name = Overture;
Expand All @@ -151,6 +159,7 @@
OBJ_32 /* PipeTests.swift */,
OBJ_33 /* UncurryTests.swift */,
OBJ_34 /* WithTests.swift */,
DCF82371212684BE000A7F94 /* ZipTests.swift */,
);
name = OvertureTests;
path = Tests/OvertureTests;
Expand Down Expand Up @@ -277,6 +286,7 @@
buildActionMask = 0;
files = (
OBJ_44 /* Chain.swift in Sources */,
DCF8237021267FD4000A7F94 /* ZipSequence.swift in Sources */,
OBJ_45 /* Compose.swift in Sources */,
OBJ_46 /* Concat.swift in Sources */,
OBJ_47 /* Curry.swift in Sources */,
Expand All @@ -287,6 +297,7 @@
OBJ_53 /* Sequence.swift in Sources */,
OBJ_54 /* Setters.swift in Sources */,
OBJ_55 /* Uncurry.swift in Sources */,
DC5E715C21065C6900ED239B /* ZipOptional.swift in Sources */,
OBJ_56 /* With.swift in Sources */,
OBJ_57 /* Zurry.swift in Sources */,
);
Expand All @@ -309,6 +320,7 @@
OBJ_77 /* ConcatTests.swift in Sources */,
OBJ_78 /* CurryTests.swift in Sources */,
OBJ_79 /* FlipTests.swift in Sources */,
DCF82372212684BE000A7F94 /* ZipTests.swift in Sources */,
OBJ_80 /* PipeTests.swift in Sources */,
OBJ_81 /* UncurryTests.swift in Sources */,
OBJ_82 /* WithTests.swift in Sources */,
Expand Down
80 changes: 77 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ A library for function composition.
- [`prop`](#prop)
- [`over` and `set`](#over-and-set)
- [`mprop`, `mver`, and `mut`](#mprop-mver-and-mut)
- [`zip`](#zip)
- [FAQ](#faq)
- [Installation](#installation)
- [🎶 Prelude](#-prelude)
Expand Down Expand Up @@ -309,6 +310,79 @@ let request = with(URLRequest(url: url), concat(
))
```

### `zip` and `zip(with:)`

This is a function that Swift ships with! Unfortunately, it's limited to pairs of sequences. Overture defines `zip` to work with up to ten sequences at once, which makes combining several sets of related data a snap.

```swift
let ids = [1, 2, 3]
let emails = ["[email protected]", "[email protected]", "[email protected]"]
let names = ["Blob", "Blob Junior", "Blob Senior"]

zip(ids, emails, names)
// [
// (1, "[email protected]", "Blob"),
// (2, "[email protected]", "Blob Junior"),
// (3, "[email protected]", "Blob Senior")
// ]
```

It's common to immediately `map` on zipped values.

``` swift
struct User {
let id: Int
let email: String
let name: String
}

zip(ids, emails, names).map(User.init)
// [
// User(id: 1, email: "[email protected]", name: "Blob"),
// User(id: 2, email: "[email protected]", name: "Blob Junior"),
// User(id: 3, email: "[email protected]", name: "Blob Senior")
// ]
```

Because of this, Overture provides a `zip(with:)` helper, which takes a tranform function up front and is curried, so it can be composed with other functions using `pipe`.

``` swift
zip(with: User.init)(ids, emails, names)
```

Overture also extends the notion of `zip` to work with optionals! It's an expressive way of combining multiple optionals together.

``` swift
let optionalId: Int? = 1
let optionalEmail: String? = "[email protected]"
let optionalName: String? = "Blob"

zip(optionalId, optionalEmail, optionalName)
// Optional<(Int, String, String)>.some((1, "[email protected]", "Blob"))
```

And `zip(with:)` lets us transform these tuples into other values.

``` swift
zip(with: User.init)(optionalId, optionalEmail, optionalName)
// Optional<User>.some(User(id: 1, email: "[email protected]", name: "Blob"))
```

Using `zip` can be an expressive alternative to `let`-unwrapping!

``` swift
let optionalUser = zip(with: User.init)(optionalId, optionalEmail, optionalName)

// vs.

let optionalUser: User?
if let id = optionalId, let email = optionalEmail, let name = optionalName {
optionalUser = User(id: id, email: email, name: name)
} else {
optionalUser = nil
}
```

## FAQ

- **Should I be worried about polluting the global namespace with free functions?**
Expand Down Expand Up @@ -336,15 +410,15 @@ let request = with(URLRequest(url: url), concat(
If you use [Carthage](https://github.com/Carthage/Carthage), you can add the following dependency to your `Cartfile`:

``` ruby
github "pointfreeco/swift-overture" ~> 0.2
github "pointfreeco/swift-overture" ~> 0.3
```

### CocoaPods

If your project uses [CocoaPods](https://cocoapods.org), just add the following to your `Podfile`:

``` ruby
pod 'Overture', '~> 0.2'
pod 'Overture', '~> 0.3'
```

### SwiftPM
Expand All @@ -353,7 +427,7 @@ If you want to use Overture in a project that uses [SwiftPM](https://swift.org/p

``` swift
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-overture.git", from: "0.2.0")
.package(url: "https://github.com/pointfreeco/swift-overture.git", from: "0.3.0")
]
```

Expand Down
20 changes: 20 additions & 0 deletions Sources/Overture/Optional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,23 @@ public func map<A, B>(

return { try $0.map(transform) }
}


/// Transforms a pair of optionals into an optional pair.
///
/// - Parameters:
/// - a: An optional value.
/// - b: Another optional value.
/// - Returns: An optional pair of values.
public func zip<A, B>(_ a: A?, _ b: B?) -> (A, B)? {
guard let a = a, let b = b else { return nil }
return (a, b)
}

/// Transforms a pair of optionals into a new optional value.
///
/// - Parameter transform: A transform function.
/// - Returns: A transformed optional value.
public func zip<A, B, C>(with transform: @escaping (A, B) -> C) -> (A?, B?) -> C? {
return { zip($0, $1).map(transform) }
}
155 changes: 155 additions & 0 deletions Sources/Overture/ZipOptional.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
public func zip<A, B, C>(
_ a: A?,
_ b: B?,
_ c: C?
)
-> (A, B, C)? {
return zip(zip(a, b), c).map { ($0.0, $0.1, $1) }
}

public func zip<A, B, C, D>(
with transform: @escaping (A, B, C) -> D
)
-> (A?, B?, C?) -> D? {
return { zip($0, $1, $2).map(transform) }
}

public func zip<A, B, C, D>(
_ a: A?,
_ b: B?,
_ c: C?,
_ d: D?
)
-> (A, B, C, D)? {
return zip(zip(a, b), c, d).map { ($0.0, $0.1, $1, $2) }
}

public func zip<A, B, C, D, E>(
with transform: @escaping (A, B, C, D) -> E
)
-> (A?, B?, C?, D?) -> E? {
return { zip($0, $1, $2, $3).map(transform) }
}

public func zip<A, B, C, D, E>(
_ a: A?,
_ b: B?,
_ c: C?,
_ d: D?,
_ e: E?
)
-> (A, B, C, D, E)? {
return zip(zip(a, b), c, d, e).map { ($0.0, $0.1, $1, $2, $3) }
}

public func zip<A, B, C, D, E, F>(
with transform: @escaping (A, B, C, D, E) -> F
)
-> (A?, B?, C?, D?, E?) -> F? {
return { zip($0, $1, $2, $3, $4).map(transform) }
}

public func zip<A, B, C, D, E, F>(
_ a: A?,
_ b: B?,
_ c: C?,
_ d: D?,
_ e: E?,
_ f: F?
)
-> (A, B, C, D, E, F)? {
return zip(zip(a, b), c, d, e, f).map { ($0.0, $0.1, $1, $2, $3, $4) }
}

public func zip<A, B, C, D, E, F, G>(
with transform: @escaping (A, B, C, D, E, F) -> G
)
-> (A?, B?, C?, D?, E?, F?) -> G? {
return { zip($0, $1, $2, $3, $4, $5).map(transform) }
}

public func zip<A, B, C, D, E, F, G>(
_ a: A?,
_ b: B?,
_ c: C?,
_ d: D?,
_ e: E?,
_ f: F?,
_ g: G?
)
-> (A, B, C, D, E, F, G)? {
return zip(zip(a, b), c, d, e, f, g).map { ($0.0, $0.1, $1, $2, $3, $4, $5) }
}

public func zip<A, B, C, D, E, F, G, H>(
with transform: @escaping (A, B, C, D, E, F, G) -> H
)
-> (A?, B?, C?, D?, E?, F?, G?) -> H? {
return { zip($0, $1, $2, $3, $4, $5, $6).map(transform) }
}

public func zip<A, B, C, D, E, F, G, H>(
_ a: A?,
_ b: B?,
_ c: C?,
_ d: D?,
_ e: E?,
_ f: F?,
_ g: G?,
_ h: H?
)
-> (A, B, C, D, E, F, G, H)? {
return zip(zip(a, b), c, d, e, f, g, h).map { ($0.0, $0.1, $1, $2, $3, $4, $5, $6) }
}

public func zip<A, B, C, D, E, F, G, H, I>(
with transform: @escaping (A, B, C, D, E, F, G, H) -> I
)
-> (A?, B?, C?, D?, E?, F?, G?, H?) -> I? {
return { zip($0, $1, $2, $3, $4, $5, $6, $7).map(transform) }
}

public func zip<A, B, C, D, E, F, G, H, I>(
_ a: A?,
_ b: B?,
_ c: C?,
_ d: D?,
_ e: E?,
_ f: F?,
_ g: G?,
_ h: H?,
_ i: I?
)
-> (A, B, C, D, E, F, G, H, I)? {
return zip(zip(a, b), c, d, e, f, g, h, i).map { ($0.0, $0.1, $1, $2, $3, $4, $5, $6, $7) }
}

public func zip<A, B, C, D, E, F, G, H, I, J>(
with transform: @escaping (A, B, C, D, E, F, G, H, I) -> J
)
-> (A?, B?, C?, D?, E?, F?, G?, H?, I?) -> J? {
return { zip($0, $1, $2, $3, $4, $5, $6, $7, $8).map(transform) }
}

public func zip<A, B, C, D, E, F, G, H, I, J>(
_ a: A?,
_ b: B?,
_ c: C?,
_ d: D?,
_ e: E?,
_ f: F?,
_ g: G?,
_ h: H?,
_ i: I?,
_ j: J?
)
-> (A, B, C, D, E, F, G, H, I, J)? {
return zip(zip(a, b), c, d, e, f, g, h, i, j).map { ($0.0, $0.1, $1, $2, $3, $4, $5, $6, $7, $8) }
}

public func zip<A, B, C, D, E, F, G, H, I, J, K>(
with transform: @escaping (A, B, C, D, E, F, G, H, I, J) -> K
)
-> (A?, B?, C?, D?, E?, F?, G?, H?, I?, J?) -> K? {
return { zip($0, $1, $2, $3, $4, $5, $6, $7, $8, $9).map(transform) }
}
Loading

0 comments on commit 1632b5d

Please sign in to comment.