Created
February 17, 2015 06:33
-
-
Save jlieske/3a5aa4e72e3467064544 to your computer and use it in GitHub Desktop.
Monadic while loop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // TryThenWhile.swift | |
| // TryThenWhile | |
| // | |
| // Created by Jay Lieske on 2015.02.16. | |
| // Copyright (c) 2015 Jay Lieske. All rights reserved. | |
| // | |
| import Cocoa | |
| import XCTest | |
| /// Object wrapper to work around Swift 1.1 limitations with generic enums. | |
| public final class Box<T> { | |
| /// The wrapped value. | |
| public let unbox: T | |
| /// Wrap a value. | |
| public init(_ value: T) { | |
| self.unbox = value | |
| } | |
| } | |
| func ==<T : Equatable>(lhs: Box<T>, rhs: Box<T>) -> Bool { | |
| return lhs.unbox == rhs.unbox | |
| } | |
| /// An Either type for Foundation programming. | |
| /// The Success case holds the value of a computation. | |
| /// The Failure case holds an NSError. | |
| public enum Result<SuccessType> { | |
| /// Represents a successful computation. | |
| /// Wrapped in a Box because of Swift 1.1 limitations. | |
| case Success(Box<SuccessType>) | |
| /// Represents a failed computation. | |
| case Failure(NSError) | |
| /// Create a Success. | |
| public init(_ value: SuccessType) { | |
| self = .Success(Box(value)) | |
| } | |
| /// Create a Failure. | |
| public init(error: NSError) { | |
| self = .Failure(error) | |
| } | |
| /// Apply a function if Success | |
| public func map<U>(f: (SuccessType) -> U) -> Result<U> { | |
| switch self { | |
| case Success(let boxedValue): | |
| return .Success(Box(f(boxedValue.unbox))) | |
| case Failure(let error): | |
| return .Failure(error) | |
| } | |
| } | |
| /// Apply a function if Success | |
| public func flatMap<U>(f: (SuccessType) -> Result<U>) -> Result<U> { | |
| switch self { | |
| case Success(let boxedValue): | |
| return f(boxedValue.unbox) | |
| case Failure(let error): | |
| return .Failure(error) | |
| } | |
| } | |
| } | |
| /// Methods for Result sequencing and control flow. | |
| extension Result { | |
| /// Perform a step if Success, wrapping the result. | |
| public func then<U>(f: (SuccessType) -> U) -> Result<U> { | |
| return self.map(f) | |
| } | |
| /// Perform a step if Success. | |
| public func then<U>(f: (SuccessType) -> Result<U>) -> Result<U> { | |
| return self.flatMap(f) | |
| } | |
| /// Perform a loop if Success, breaking out of the loop on Failure. | |
| public func thenWhile(test: @autoclosure () -> Bool, body: () -> Result<()>) -> Result<()> { | |
| switch self { | |
| case Failure(let error): | |
| return .Failure(error) | |
| case Success(let _): | |
| while test() { | |
| switch body() { | |
| case Failure(let error): | |
| return .Failure(error) | |
| case Success(let _): | |
| break; | |
| } | |
| } | |
| return Result<()>(()) | |
| } | |
| } | |
| /// Perform a loop if Success. | |
| public func thenWhile(test: @autoclosure () -> Bool, body: () -> ()) -> Result<()> { | |
| switch self { | |
| case Failure(let error): | |
| return .Failure(error) | |
| case Success(let _): | |
| while test() { | |
| body() | |
| } | |
| return Result<()>(()) | |
| } | |
| } | |
| } | |
| class TryThenWhile: XCTestCase { | |
| func testResultWhile() { | |
| // Given | |
| let s1: String = "hello" | |
| let e1 = NSError(domain: "JaySwift", code: 1, userInfo: nil) | |
| // When | |
| var l1 = 0 | |
| let rs1 = Result(s1) | |
| .thenWhile (l1 < 10) { | |
| l1 += 1 | |
| } | |
| .then {l1} | |
| var l2 = 0 | |
| let fe2 = Result<String>(error: e1) | |
| .thenWhile (l2 < 10) { | |
| l2 += 1 | |
| } | |
| .then {l2} | |
| var l3 = 0 | |
| let fe3 = Result(s1) | |
| .thenWhile (l3 < 10) { | |
| Result(error: e1) | |
| } | |
| .then {l3} | |
| var l4 = 0 | |
| let rs4 = Result(s1) | |
| .thenWhile (l4 < 10) { | |
| Result(l4 += 1) | |
| } | |
| .then {l4} | |
| // Then | |
| switch rs1 { | |
| case .Success(let res): XCTAssertEqual(res.unbox, 10, "Test 1 should have succeeded") | |
| case .Failure(_): XCTFail("Test 1 failed") | |
| } | |
| switch fe2 { | |
| case .Success(_): XCTFail("Test 2 should have failed") | |
| case .Failure(_): break | |
| } | |
| switch fe3 { | |
| case .Success(_): XCTFail("Test 3 should have failed") | |
| case .Failure(_): break | |
| } | |
| switch rs4 { | |
| case .Success(let res): XCTAssertEqual(res.unbox, 10, "Test 4 should have succeeded") | |
| case .Failure(_): XCTFail("Test 4 failed") | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment