├── .gitignore ├── .travis.yml ├── Immutable.podspec ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── Immutable │ ├── Collection.swift │ ├── Dictionary.swift │ └── FilterNil.swift └── Tests ├── ImmutableTests ├── CollectionTests.swift ├── DictinoaryTests.swift └── FilterNilTests.swift └── LinuxMain.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | osx_image: xcode10.2 5 | language: generic 6 | sudo: required 7 | dist: trusty 8 | install: 9 | - eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/9f442512a46d7a2af7b850d65a7e9bd31edfb09b/swiftenv-install.sh)" 10 | - swiftenv install swift-5.0 || true 11 | script: 12 | - swift test 13 | -------------------------------------------------------------------------------- /Immutable.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'Immutable' 3 | s.version = '0.6.0' 4 | s.summary = 'Not yet implemented functions in Swift.' 5 | s.homepage = 'https://github.com/devxoul/Immutable' 6 | s.license = { :type => 'MIT', :file => 'LICENSE' } 7 | s.author = { 'Suyeol Jeon' => 'devxoul@gmail.com' } 8 | s.source = { :git => 'https://github.com/devxoul/Immutable.git', 9 | :tag => s.version.to_s } 10 | s.source_files = "Sources/**/*.swift" 11 | s.swift_version = "5.0" 12 | 13 | s.ios.deployment_target = '8.0' 14 | s.osx.deployment_target = '10.11' 15 | s.tvos.deployment_target = '9.0' 16 | s.watchos.deployment_target = '2.0' 17 | end 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Suyeol Jeon (xoul.kr) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Immutable", 7 | platforms: [ 8 | .macOS(.v10_10), .iOS(.v8), .tvOS(.v9) 9 | ], 10 | products: [ 11 | .library(name: "Immutable", targets: ["Immutable"]), 12 | ], 13 | targets: [ 14 | .target(name: "Immutable"), 15 | .testTarget(name: "ImmutableTests", dependencies: ["Immutable"]), 16 | ], 17 | swiftLanguageVersions: [.v5] 18 | ) 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Immutable 2 | 3 | ![Swift](https://img.shields.io/badge/Swift-5.0-orange.svg) 4 | [![Build Status](https://travis-ci.org/devxoul/Immutable.svg?branch=master)](https://travis-ci.org/devxoul/Immutable) 5 | [![CocoaPods](http://img.shields.io/cocoapods/v/Immutable.svg)](https://cocoapods.org/pods/Immutable) 6 | 7 | Missing immutable functions in Swift. You must be looking for somebody to make this library 😛 8 | 9 | ## Features 10 | 11 | * Non-mutating `appending()`, `inserting()`, `removing()` functions in `Collection` 12 | * `map()` and `flatMap()` for `Dictionary` 13 | * `filterNil()` for `Collection` and `Dictionary` 14 | 15 | ## Installation 16 | 17 | * **Using CocoaPods**: 18 | 19 | ```ruby 20 | pod 'Immutable' 21 | ``` 22 | 23 | * **Using Carthage**: 24 | 25 | ``` 26 | github "devxoul/Immutable" 27 | ``` 28 | 29 | * **Using Swift Package Manager**: 30 | 31 | ```swift 32 | let package = Package( 33 | name: "MyAwesomeProject", 34 | targets: [], 35 | dependencies: [ 36 | .Package(url: "https://github.com/devxoul/Immutable.git", majorVersion: 0) 37 | ] 38 | ) 39 | ``` 40 | 41 | ## Requirements 42 | 43 | * Swift 3 44 | 45 | ## Contribution 46 | 47 | Any discussions and pull requests are welcomed 💖 48 | 49 | Use `$ swift generate-xcodeproj` to generate Xcode project for development. 50 | 51 | ## License 52 | 53 | Immutable is under MIT license. See the [LICENSE](LICENSE) for more info. 54 | -------------------------------------------------------------------------------- /Sources/Immutable/Collection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Collection.swift 3 | // Immutable 4 | // 5 | // Created by Suyeol Jeon on 12/04/2017. 6 | // 7 | // 8 | 9 | extension RangeReplaceableCollection { 10 | /// Returns a new collection by appending a new element. 11 | public func appending(_ newElement: Self.Iterator.Element) -> Self { 12 | var copy = self 13 | copy.append(newElement) 14 | return copy 15 | } 16 | 17 | /// Returns a new collection by appending new elements. 18 | public func appending(contentsOf newElements: S) -> Self where S: Sequence, S.Iterator.Element == Self.Iterator.Element { 19 | var copy = self 20 | copy.append(contentsOf: newElements) 21 | return copy 22 | } 23 | 24 | /// Returns a new collection by inserting a new element. 25 | public func inserting(_ newElement: Self.Iterator.Element, at i: Self.Index) -> Self { 26 | var copy = self 27 | copy.insert(newElement, at: i) 28 | return copy 29 | } 30 | 31 | /// Returns a new collection by inserting new elements. 32 | public func inserting(contentsOf newElements: C, at i: Self.Index) -> Self where C: Collection, C.Iterator.Element == Self.Iterator.Element { 33 | var copy = self 34 | copy.insert(contentsOf: newElements, at: i) 35 | return copy 36 | } 37 | 38 | /// Returns a new collection by removing an element at specified index. 39 | public func removing(at i: Self.Index) -> Self { 40 | var copy = self 41 | copy.remove(at: i) 42 | return copy 43 | } 44 | 45 | /// Returns a new collection by removing all elements. 46 | public func removingAll(keepingCapacity: Bool = false) -> Self { 47 | var copy = self 48 | copy.removeAll(keepingCapacity: keepingCapacity) 49 | return copy 50 | } 51 | 52 | /// Returns a new collection by removing first element. 53 | public func removingFirst(_ n: Int = 1) -> Self { 54 | var copy = self 55 | copy.removeFirst(n) 56 | return copy 57 | } 58 | 59 | /// Returns a new collection by replacing given subrange with new elements. 60 | public func replacingSubrange(_ subrange: Range, with newElements: C) -> Self where C : Collection, C.Iterator.Element == Self.Iterator.Element { 61 | var copy = self 62 | copy.replaceSubrange(subrange, with: newElements) 63 | return copy 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Immutable/Dictionary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary.swift 3 | // Immutable 4 | // 5 | // Created by Suyeol Jeon on 03/03/2017. 6 | // 7 | // 8 | 9 | extension Dictionary { 10 | /// Initialize dictionary with a collection of key-value tuples. 11 | public init(elements: C) where C: Collection, C.Iterator.Element == (Key, Value) { 12 | self.init() 13 | for (key, value) in elements { 14 | self[key] = value 15 | } 16 | } 17 | 18 | /// `map()` that returns `Dictionary`. 19 | public func map(_ transform: (Key, Value) throws -> (T, U)) rethrows -> [T: U] where T: Hashable { 20 | return Dictionary(elements: try self.map(transform)) 21 | } 22 | 23 | /// `flatMap()` that returns `Dictionary`. 24 | public func flatMap(_ transform: (Key, Value) throws -> (T, U)?) rethrows -> [T: U] where T: Hashable { 25 | return Dictionary(elements: try self.compactMap(transform)) 26 | } 27 | 28 | /// Returns a new dictionary by updating a value for key. 29 | public func updatingValue(_ value: Value, forKey key: Key) -> Dictionary { 30 | var copy = self 31 | copy.updateValue(value, forKey: key) 32 | return copy 33 | } 34 | 35 | /// Returns a new dictionary by removing a value for key. 36 | public func removingValue(forKey key: Key) -> Dictionary { 37 | var copy = self 38 | copy.removeValue(forKey: key) 39 | return copy 40 | } 41 | 42 | /// returns a new directory by merging all values for respective keys. 43 | public func merging(_ dictionary: Dictionary) -> Dictionary { 44 | var copy = self 45 | dictionary.forEach { copy.updateValue($1, forKey: $0) } 46 | return copy 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/Immutable/FilterNil.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterNil.swift 3 | // Immutable 4 | // 5 | // Created by Suyeol Jeon on 03/03/2017. 6 | // 7 | // 8 | 9 | /// A protocol for making generic type constraints of Optionals. 10 | public protocol _OptionalType { 11 | associatedtype _Wrapped 12 | 13 | func flatMap(_ transform: (_Wrapped) throws -> U?) rethrows -> U? 14 | } 15 | 16 | extension Optional: _OptionalType { 17 | public typealias _Wrapped = Wrapped 18 | } 19 | 20 | extension Collection where Iterator.Element: _OptionalType { 21 | /// Returns nil-excluded array. 22 | public func filterNil() -> [Iterator.Element._Wrapped] { 23 | return self.compactMap { $0.flatMap { $0 } } 24 | } 25 | } 26 | 27 | extension Dictionary where Value: _OptionalType { 28 | public func filterNil() -> [Key: Value._Wrapped] { 29 | return self.flatMap { key, value in 30 | return value.flatMap { (key, $0) } 31 | } 32 | } 33 | } 34 | 35 | public func filterNil(_ array: [T?]) -> [T] { 36 | return array.filterNil() 37 | } 38 | 39 | public func filterNil(_ dictionary: [K: V?]) -> [K: V] { 40 | return dictionary.filterNil() 41 | } 42 | -------------------------------------------------------------------------------- /Tests/ImmutableTests/CollectionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionTests.swift 3 | // Immutable 4 | // 5 | // Created by Suyeol Jeon on 13/04/2017. 6 | // 7 | // 8 | 9 | import XCTest 10 | import Immutable 11 | 12 | final class CollectionTests: XCTestCase { 13 | 14 | func testAppending() { 15 | XCTAssertEqual([1, 2, 3].appending(4), [1, 2, 3, 4]) 16 | XCTAssertEqual([1, 2, 3].appending(contentsOf: [4, 5]), [1, 2, 3, 4, 5]) 17 | } 18 | 19 | func testInserting() { 20 | XCTAssertEqual([1, 2, 3].inserting(4, at: 1), [1, 4, 2, 3]) 21 | XCTAssertEqual([1, 2, 3].inserting(contentsOf: [4, 5], at: 1), [1, 4, 5, 2, 3]) 22 | } 23 | 24 | func testRemoving() { 25 | XCTAssertEqual([1, 2, 3].removing(at: 0), [2, 3]) 26 | XCTAssertEqual([1, 2, 3].removingAll(), []) 27 | XCTAssertEqual([1, 2, 3].removingFirst(), [2, 3]) 28 | XCTAssertEqual([1, 2, 3].removingFirst(2), [3]) 29 | } 30 | 31 | func testReplacing() { 32 | XCTAssertEqual([1, 2, 3].replacingSubrange(0..<2, with: []), [3]) 33 | XCTAssertEqual([1, 2, 3].replacingSubrange(0..<2, with: [0, 1]), [0, 1, 3]) 34 | XCTAssertEqual([1, 2, 3].replacingSubrange(0..<2, with: [0, 1, 2]), [0, 1, 2, 3]) 35 | } 36 | 37 | static var allTests : [(String, (CollectionTests) -> () throws -> Void)] { 38 | return [ 39 | ("testAppending", testAppending), 40 | ("testInserting", testInserting), 41 | ("testRemoving", testRemoving), 42 | ("testReplacing", testReplacing), 43 | ] 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Tests/ImmutableTests/DictinoaryTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DictionaryTests.swift 3 | // Immutable 4 | // 5 | // Created by Suyeol Jeon on 03/03/2017. 6 | // 7 | // 8 | 9 | import XCTest 10 | import Immutable 11 | 12 | class DictionaryTests: XCTestCase { 13 | 14 | func testMap() { 15 | let dict: [String: Int] = [ 16 | "a": 1, 17 | "b": 2, 18 | "c": 3, 19 | ] 20 | let result = dict.map { key, value in 21 | return (key.uppercased(), "\(value * 2)") 22 | } 23 | XCTAssertEqual(result.count, 3) 24 | XCTAssertEqual(result["A"], "2") 25 | XCTAssertEqual(result["B"], "4") 26 | XCTAssertEqual(result["C"], "6") 27 | } 28 | 29 | func testFlatMap() { 30 | let dict: [String: Int] = [ 31 | "a": 1, 32 | "b": 2, 33 | "c": 3, 34 | ] 35 | let result = dict.flatMap { key, value -> (String, String)? in 36 | if value % 2 == 0 { 37 | return nil 38 | } else { 39 | return (key.uppercased(), "\(value * 2)") 40 | } 41 | } 42 | XCTAssertEqual(result.count, 2) 43 | XCTAssertEqual(result["A"], "2") 44 | XCTAssertNil(result["B"]) 45 | XCTAssertEqual(result["C"], "6") 46 | } 47 | 48 | func testUpdatingValue() { 49 | XCTAssertEqual(["a": 10].updatingValue(20, forKey: "b"), ["a": 10, "b": 20]) 50 | XCTAssertEqual(["a": 10].updatingValue(20, forKey: "a"), ["a": 20]) 51 | } 52 | 53 | func testRemovingValue() { 54 | XCTAssertEqual(["a": 10].removingValue(forKey: "a"), [:]) 55 | } 56 | 57 | func testMergingValues() { 58 | XCTAssertEqual(["a": 10, "b": 20].merging(["b": 30, "c": 40]), 59 | ["a": 10, "b": 30, "c": 40]) 60 | } 61 | 62 | 63 | static var allTests : [(String, (DictionaryTests) -> () throws -> Void)] { 64 | return [ 65 | ("testMap", testMap), 66 | ("testFlatMap", testFlatMap), 67 | ] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/ImmutableTests/FilterNilTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterNilTests.swift 3 | // Immutable 4 | // 5 | // Created by Suyeol Jeon on 03/03/2017. 6 | // 7 | // 8 | 9 | import XCTest 10 | import Immutable 11 | 12 | class FilterNilTests: XCTestCase { 13 | 14 | func testFilterNil_array() { 15 | let array: [Int?] = [1, 2, 3, nil, 5, nil, 7] 16 | let result = array.filterNil() 17 | XCTAssertEqual(result, [1, 2, 3, 5, 7]) 18 | } 19 | 20 | func testFilterNil_dictionary() { 21 | let dict: [String: Int?] = [ 22 | "a": 1, 23 | "b": nil, 24 | "c": 3, 25 | "d": 4, 26 | "e": nil, 27 | ] 28 | let result = dict.filterNil() 29 | XCTAssertEqual(result, [ 30 | "a": 1, 31 | "c": 3, 32 | "d": 4, 33 | ]) 34 | } 35 | 36 | 37 | static var allTests : [(String, (FilterNilTests) -> () throws -> Void)] { 38 | return [ 39 | ("testFilterNil_array", testFilterNil_array), 40 | ("testFilterNil_dictionary", testFilterNil_dictionary), 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import ImmutableTests 3 | 4 | XCTMain([ 5 | testCase(CollectionTests.allTests), 6 | testCase(DictionaryTests.allTests), 7 | testCase(FilterNilTests.allTests), 8 | ]) 9 | --------------------------------------------------------------------------------