Skip to content

Instantly share code, notes, and snippets.

@robb
Forked from erica/0. Original Code.swift
Last active August 29, 2015 14:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robb/1639325aa8784c4bbc29 to your computer and use it in GitHub Desktop.
Save robb/1639325aa8784c4bbc29 to your computer and use it in GitHub Desktop.
import Foundation
typealias JSONDictionary = [String: AnyObject]
/// Attempts to load a data from a given URL.
func loadData(URL: NSURL) -> Either<NSError, NSData> {
if let data = NSData(contentsOfURL: URL) {
return Either.Right(Box(data))
} else {
// In a real world scenario, we'd have a more useful error :-)
return Either.Left(Box(NSError()))
}
}
/// Attempts to parse JSON from NSData.
func parseJSON(data: NSData) -> Either<NSError, JSONDictionary> {
var error: NSError?
if let dictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0), error: nil) as? JSONDictionary {
return Either.Right(Box(dictionary))
} else {
// In a real world scenario, we'd have a more useful fallback error :-)
return Either.Left(Box(error ?? NSError()))
}
}
let appIDs = [ "690046600" ]
let JSONResults = appIDs
/// Build the URLs:
|> map { NSURL(string: "https://itunes.apple.com/lookup?id=\($0)")! }
/// Download the data and attempt to parse the JSON, passing all errors
/// along:
|> map { loadData($0) |> flatMap(parseJSON) }
/// Strip all errors by filtering them out and force-unwrapping the
/// remaining elements:
|> filter { $0.right != nil }
|> map { $0.right! }
let info = JSONResults
/// Extract the data that we need from the remaining JSON dictionaries:
|> map { (JSON: JSONDictionary) -> (String, String, String) in
let appInfo = JSON
|> flatMap { $0["results"] as? [JSONDictionary] }
|> flatMap { $0[0] }
let name = appInfo
|> flatMap { $0["trackName"] as? String }
let price = appInfo
|> flatMap { $0["formattedPrice"] as? String }
let rating = appInfo
|> flatMap {
$0["averageUserRating"] ?? $0["trackContentRating"] as? NSNumber
}
|> map { "\($0)" }
return (name ?? "Unknown App", price ?? "Unknown Price", rating ?? "Rating not available")
}
println(info)
/// The application operator allows us to chain free functions as if they were
/// member functions, taken from https://github.com/robrix/Prelude
infix operator |> {
associativity left
precedence 95
}
public func |> <T, U> (left: T, @noescape right: T -> U) -> U {
return right(left)
}
/// Box let's us to have type-parameterized enums where more than one case has
/// a value, taken from https://github.com/robrix/Box
public final class Box<T> {
public let value: T
public init(_ value: T) {
self.value = value
}
}
/// Either allows us to specify that a value should be one of two types, or that
/// that a value of a single type should have one of two semantics.
/// From https://github.com/robrix/Either
public enum Either<T, U> {
case Left(Box<T>)
case Right(Box<U>)
public var left: T? {
switch self {
case let .Left(x):
return x.value
case let .Right(_):
return .None
}
}
public var right: U? {
switch self {
case let .Left(_):
return .None
case let .Right(x):
return x.value
}
}
}
/// Flipped variants of function we'll need. By having a closure as the first,
/// curried argument, we can chain them more elegantly with |>.
public func filter<S : SequenceType>(includeElement: (S.Generator.Element) -> Bool)(source: S) -> [S.Generator.Element] {
return filter(source, includeElement)
}
public func flatMap<T, U>(f: (T) -> U?)(x: T?) -> U? {
return flatMap(x, f)
}
public func flatMap<T, U, V>(transform: U -> Either<T, V>)(either: Either<T, U>) -> Either<T, V> {
switch either {
case let .Left(x):
return Either.Left(x)
case let .Right(x):
return transform(x.value)
}
}
public func map<T, U>(f: (T) -> U)(x: T?) -> U? {
return map(x, f)
}
public func map<S : SequenceType, T>(transform: (S.Generator.Element) -> T)(source: S) -> [T] {
return map(source, transform)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment