Second Bridge is a Swift framework for functional programming. Our goal is to make Swift development on par with other functional languages like Scala by adding new data types, functions and operators.
Second Bridge is a Swift framework for functional programming. Our goal is to make Swift development on par with other functional languages like Scala by adding new data types, functions and operators.
This project depends on the awesome Swiftz framework, that defines functional data structures, functions, idioms, and extensions that augment the Swift standard library.
Second Bridge supports the CocoaPods dependency manager. To install the framework in your project, place the following in your Podfile:
platform :ios, '8.0'
use_frameworks!
// (... other pods ...)
pod 'SecondBridge', :git => 'https://github.com/47deg/second-bridge.git'
By running pod install
or pod update
you should have Second Bridge in your workspace ready to go!
Traversable
Protocols like Traversable and Iterable will make it easier for you to expand the current data-types available. If you need to create a new data-type, just by implementing the following three methods your type will have access to the 40-something functions available in Second Bridge:
// Traverse all items of the instance, and call the provided function on each one.
func foreach(f: (ItemType) -> ())
// Build a new instance of the same Traversable type with the elements contained
// in the `elements` array (i.e.: returned from the **T functions).
class func build(elements: [ItemType]) -> Self
// Build a new instance of the same Traversable type with the elements contained in the provided
// Traversable instance. Users calling this function are responsible of transforming the data of each
// item to a valid ItemType suitable for the current Traversable class.
class func buildFromTraversable<U where U : Traversable>(traversable: U) -> Self
The following global functions are available for any Traversable-conforming type. Those are based on the main ones available in Scala for Traversable-derived types:
f
over a set of the elements of this Traversable that match the condition defined in f
’s isDefinedAt
.p
, if any.includeElement
closure.excludeElement
closure.transform
on each element of the traversable, and then flattening the results into an array. You can create a new Traversable from the results of the flatMap application by calling function Traversable.build and passing its results to it.initial
and each element of the current traversable from right to left. A reversal equivalent to reduceT
/foldLeftT
.initial
and each element of the current traversable from left to right. Equivalent to reduceT
.f
.transform
over the elements of the provided Traversable.transform
over its elements. The resulting elements are guaranteed to be the same type as the items of the provided traversable.start
and end
strings.initial
and each element of the current traversable.source
which satisfy the invariant: from <= indexOf(x) < until
.p
.p
, while the second contains all elements after those.Iterable
Any Traversable-conforming data type that also implements the standard SequenceType protocol (defining an iterator to step one-by-one through the collection’s elements) instantly becomes an Iterable. Iterables have instant access to the next functions, also based on their Scala counter-parts:
n
, comprising all the elements of the provided Iterable.n
while traversing through a sliding window of size windowSize
.n
while traversing through a sliding window of size 1.ArrayT
An immutable, traversable and typed Array.
import SecondBridge
let anArray : ArrayT<Int> = [1, 2, 3, 4, 5, 6]
let list = anArray.toList()
anArray.isEmpty() // False
anArray.size() // 6
anArray.drop(4) // [5,6]
anArray.filterNot({ $0 == 1 }) // [2, 3, 4, 5, 6]
BinarySearchTree
An immutable binary search tree.
import SecondBridge
let tree = BinarySearchTree.Node(2, left: BinarySearchTree.Empty, right: BinarySearchTree.Empty) // [. 2 .]
let tree2 = tree.add(4).add(8).add(5).add(1).add(3).add(7).add(10) // [[. 1 .] 2 [[. 3 .] 4 [[. 5 [. 7 .]] 8 [. 10 .]]]]
let tree3 = tree.remove(5) // [[. 1 .] 2 [[. 3 .] 4 [[. 7 .] 8 [. 10 .]]]]
tree2.count() // 8
tree2.search(1) // true
tree2.inOrderTraversal() // [1, 2, 3, 4, 5, 7, 8, 10]
ListT
An immutable, traversable and typed List.
import SecondBridge
let a : ListT<Int> = [1,2,3,4]
a.head() // 1
a.tail() // [2,3,4]
a.length() // 4
a.filter({$0 % 3 == 0}) // [1,2,4]
Interactive Playground about Lists
Map
An immutable, unordered, traversable and iterable collection containing pairs of keys and values. Values are typed, but Second Bridge supports several types of keys within one Map (i.e.: Int, Float and String) inside a container called HashableAny
.
import SecondBridge
let map : Map<Int> = ["a" : 1, 2 : 2, 4.5 : 3]
let map2 = map + ["c" : 4] // map2 = ["a" : 1, 2 : 2, 4.5 : 3, "c" : 4]
let map3 = map2 + ("d", 5) // map3 = ["a" : 1, 2 : 2, 4.5 : 3, "c" : 4, "d" : 5]
let map4 = map3 + [("foo", 7), ("bar", 8)] // map4 = ["a" : 1, 2 : 2, 4.5 : 3, "c" : 4, "d" : 5, "foo" : 7, "bar" : 8]
let map5 = map4 - "d" // map5 = ["a" : 1, 2 : 2, 4.5 : 3, "c" : 4, "foo" : 7, "bar" : 8]
let map 6 = map5 -- ["foo", "bar"] // map6 = ["a" : 1, 2 : 2, 4.5 : 3, "c" : 4]
let filteredMap = map6.filter({ (value) -> Bool in (value as Int) < 3}) // ("a" : 1, 2 : 2)
let reducedResult = map6.reduceByValue(0, combine: +) // 10
let values = map6.values // [1, 2, 3, 4]
Interactive Playground about Maps
Stack
An immutable, traversable, iterable and typed LIFO stack.
import SecondBridge
let stack = Stack<Int>()
let stack2 = stack.push(1).push(2).push(3) // top -> 3, 2, 1 <- bottom
let topStack = stack2.top() // 3
let popStack = stack2.pop() // (3, Stack[2, 1])
Vector
An immutable, traversable, iterable and typed Persistent Bit-partitioned Vector Trie, based on Haskell and Scala’s Vector implementations.
import SecondBridge
let vector = Vector<Int>() // Empty vector
vector = vector.append(1) // [1]
vector = vector + 2 // [1, 2]
vector += 3 // [1, 2, 3]
let value = vector[1] // 2
vector = vector.pop() // [1, 2]
Interactive Playground about Vectors
Partial Function
A partial function are those whose execution is restricted to a certain set of values, defined by the method isDefinedAt
. This allows developers to set certain conditions to their functions. An easy to understand example is a divider function, whose execution is restricted to dividers equal to zero. As with Scala’s partial functions, you are allowed to execute them even by using their restricted set of parameters. But you have access to the isDefinedAt
function that tells you if it’s safe to call.
Second Bridge defines several custom operators that makes creating partial functions really easy. Just by joining two closures with the |->
operator, we create a partial function (the first closure must return a Bool, thus defining the conditions under which this function works).
One important aspect of partial functions is that by combining them you can create meaningful pieces of code that define algorithms. i.e.: you can create a combined function that performs an operation or another, depending on the received parameter. You have access to other custom operators like |||>
(OR), >>>
(AND THEN), and ∫
(function definition). One quick example:
import SecondBridge
let doubleEvens = { $0 % 2 == 0 } |-> { $0 * 2 } // Multiply by 2 any even number
let tripleOdds = { $0 % 2 != 0 } |-> { $0 * 3 } // Multiply by 3 any odd number
let addFive = ∫(+5) // Regular function to add five to any number
let opOrElseOp = doubleEvens |||> tripleOdds // If receiving an even, double it. If not, triple it.
let opOrElseAndThenOp = doubleEvens |||> tripleOdds >>> addFive // If receiving an even, double it. If not, triple it. Then, add five to the result.
opOrElseOp.apply(3) // 9
opOrElseOp.apply(4) // 8
opOrElseAndThenOp.apply(3) // 14
opOrElseAndThenOp.apply(4) // 13
Partial functions also gives us the ability to perform complex pattern matching sets, more powerful than Swift’s standard switch block, by using our match function:
import SecondBridge
let matchTest = match({(item: Int) -> Bool in item == 0 } |-> {(Int) -> String in return "Zero"},
{(item: Int) -> Bool in item == 1 } |-> {(Int) -> String in return "One"},
{(item: Int) -> Bool in item == 2 } |-> {(Int) -> String in return "Two"},
{(item: Int) -> Bool in item > 2 } |-> {(Int) -> String in return "Moar!"})
matchTest.apply(0) // "Zero"
matchTest.apply(1) // "One"
matchTest.apply(1000) // "Moar!"
Try
Try
is a monad that encapsulates an operation that can fail and throw an exception. As you may be aware, Swift now supports do-try-catch
blocks to handle operations that can fail. Try
can wrap throwable functions (those marked with the throws
keyword) to handle these failures for you. The result of the wrapped operation is stored in a TryMatcher.Success(x)
if it’s a valid result x
, or TryMatcher.Failure(ex)
if the operation has thrown an ex
exception.
import SecondBridge
// Throwable function
func convertStringToInt(s: String) throws -> Int {
if let parsedInt = Int(s) {
return parsedInt
} else {
throw ParseError.InvalidString
}
}
let tryParseCorrectString = Try<Int>(try self.convertStringToInt("47"))
tryParseCorrectString.isFailure() // false
tryParseCorrectString.isSuccess() // true
// You can get the result of the operations through pattern matching...
switch tryParseCorrectString.matchResult {
case .Success(let value): print("Result is \(value)")
case .Failure(let ex): print("There has been an exception!")
}
// ...or using convenience functions like getOrElse
let value = tryParseCorrectString.getOrElse(0) // 47
let tryParseIncorrectString = Try<Int>(try self.convertStringToInt("47 Degrees"))
tryParseCorrectString.isFailure() // true
tryParseCorrectString.isSuccess() // false
let invalidValue = tryParseIncorrectString.getOrElse(666) // 666
// You can apply several Higher-Order Functions to Try instances
// to apply functions to the encapsulated values:
let f = { (n: Int) -> Int in n + 10 }
let mapCorrectResult = tryParseCorrectString.map(f).getOrElse(666) // 57
let filterCorrectResult =
tryParseCorrectString.filter({ $0 != 47 }) // .Failure(exception)
func tryHalf(n: Int) -> Try<Int> {
// Returns a Try containing a function that divides any Int by two
// ...
}
let flatmapCorrectResultAgainstOKFunction = tryParseCorrectString.flatMap(tryHalf)
flatmapCorrectResultAgainstOKFunction.isSuccess() // true
flatmapCorrectResultAgainstOKFunction.getOrElse(1) // 23
// You can also use `recover` and `recoverWith` to chain a set of
// Partial Functions that can handle failures in your `Try`s:
let recoverResult = tryParseIncorrectString.recover({
(e: ErrorType) -> Bool in
return true
} |-> {(e: ErrorType) -> (Int) in return 0})
recoverResult.isSuccess() // true
let recoverResultGet = recoverResult.getOrElse(1) // 0
Second Bridge supports iOS 8.0+ and Swift 2.0.
We’ve tried to pack Second Bridge with many useful features from the Scala language. But considering all the work done there, we have a lot of ground to cover yet. That’s why 47 Degrees wants YOU! Second Bridge is completely open-source, and we’re really looking forward to see what you can come up with! So if you’re interested in Second Bridge and think that you’ve come up with a great feature to add… don’t hesitate and send us a PR! We’ll review and incorporate it to our code-base as soon as possible.
Copyright © 2015 47 Degrees, LLC http://47deg.com [email protected]
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.