diff --git a/Bow.xcodeproj/project.pbxproj b/Bow.xcodeproj/project.pbxproj index b7e194faf2..05923f34c1 100644 --- a/Bow.xcodeproj/project.pbxproj +++ b/Bow.xcodeproj/project.pbxproj @@ -379,11 +379,14 @@ B58D2CA32540029F0099D426 /* MonadTrans.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58D2CA22540029F0099D426 /* MonadTrans.swift */; }; B58D2CFF254003900099D426 /* MonadTransLaws.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58D2CA4254002AB0099D426 /* MonadTransLaws.swift */; }; B58D2D17254005A60099D426 /* ComonadTransLaws.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58D2D16254005A60099D426 /* ComonadTransLaws.swift */; }; + B598643E2558768500784952 /* PairK.swift in Sources */ = {isa = PBXBuildFile; fileRef = B598643D2558768500784952 /* PairK.swift */; }; B5BF5086252B0F4A0060AD0D /* LazyFunction1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF5085252B0F4A0060AD0D /* LazyFunction1.swift */; }; B5BF50F7252B24AF0060AD0D /* LazyFunction1Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF509D252B23D30060AD0D /* LazyFunction1Test.swift */; }; B5BF510F252B253D0060AD0D /* LazyFunction1+Gen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF510E252B253D0060AD0D /* LazyFunction1+Gen.swift */; }; + B5FD89342559382A0027C335 /* PairKTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FD89332559382A0027C335 /* PairKTest.swift */; }; B5D4BF262550513600C1A661 /* MonadComprehensionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4BF252550513600C1A661 /* MonadComprehensionTest.swift */; }; B5DFCB512553FCD2008D3546 /* Exists+Gen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DFCB502553FCD2008D3546 /* Exists+Gen.swift */; }; + B5FD894D255939F60027C335 /* PairK+Gen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FD894C255939F60027C335 /* PairK+Gen.swift */; }; B8B910C0234D7F2600E44271 /* Semiring.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B910BF234D7F2600E44271 /* Semiring.swift */; }; B8B910C4234D846900E44271 /* SemiringLaws.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B910C3234D846900E44271 /* SemiringLaws.swift */; }; B8B910C6234DDA4000E44271 /* BoolInstancesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B910C5234DDA4000E44271 /* BoolInstancesTest.swift */; }; @@ -1270,11 +1273,14 @@ B58D2CA22540029F0099D426 /* MonadTrans.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MonadTrans.swift; sourceTree = ""; }; B58D2CA4254002AB0099D426 /* MonadTransLaws.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MonadTransLaws.swift; sourceTree = ""; }; B58D2D16254005A60099D426 /* ComonadTransLaws.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComonadTransLaws.swift; sourceTree = ""; }; + B598643D2558768500784952 /* PairK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairK.swift; sourceTree = ""; }; B5BF5085252B0F4A0060AD0D /* LazyFunction1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyFunction1.swift; sourceTree = ""; }; B5BF509D252B23D30060AD0D /* LazyFunction1Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyFunction1Test.swift; sourceTree = ""; }; B5BF510E252B253D0060AD0D /* LazyFunction1+Gen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "LazyFunction1+Gen.swift"; sourceTree = ""; }; + B5FD89332559382A0027C335 /* PairKTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PairKTest.swift; sourceTree = ""; }; B5D4BF252550513600C1A661 /* MonadComprehensionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonadComprehensionTest.swift; sourceTree = ""; }; B5DFCB502553FCD2008D3546 /* Exists+Gen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Exists+Gen.swift"; sourceTree = ""; }; + B5FD894C255939F60027C335 /* PairK+Gen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PairK+Gen.swift"; sourceTree = ""; }; B8B910BF234D7F2600E44271 /* Semiring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Semiring.swift; sourceTree = ""; }; B8B910C3234D846900E44271 /* SemiringLaws.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SemiringLaws.swift; sourceTree = ""; }; B8B910C5234DDA4000E44271 /* BoolInstancesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoolInstancesTest.swift; sourceTree = ""; }; @@ -1488,6 +1494,7 @@ 112097D122A8FFC0007F3D9C /* Moore+Gen.swift */, 112097BA22A7FC86007F3D9C /* NonEmptyArray+Gen.swift */, 112097BC22A7FD6F007F3D9C /* Option+Gen.swift */, + B5FD894C255939F60027C335 /* PairK+Gen.swift */, 112097D522A904AC007F3D9C /* Sum+Gen.swift */, B578BCA925385B9200B8FD42 /* Trampoline+Gen.swift */, B5618784252CC3EA002717B1 /* Tree+Gen.swift */, @@ -1983,6 +1990,7 @@ 113746A223435BA300D9C1AD /* DictionaryKTest.swift */, 1137469F234359C000D9C1AD /* DictionaryTest.swift */, 8BA0F3A9217E2DEF00969984 /* EitherKTest.swift */, + B5FD89332559382A0027C335 /* PairKTest.swift */, 8BA0F3AB217E2DEF00969984 /* EitherTest.swift */, 720C1E0323FD80AB001C5B7D /* EndoTest.swift */, B55F52B125247256001979EE /* ExistsTests.swift */, @@ -2121,6 +2129,7 @@ 8BA0F479217E2E9200969984 /* NonEmptyArray.swift */, 8BA0F478217E2E9200969984 /* Option.swift */, 09CEF850236E28680070CF43 /* Pairing.swift */, + B598643D2558768500784952 /* PairK.swift */, 1123686E240FC316005D4CF4 /* Puller.swift */, 8BA0F473217E2E9200969984 /* Reader.swift */, 8BA0F482217E2E9200969984 /* Result.swift */, @@ -2929,6 +2938,7 @@ 112097DD22A90899007F3D9C /* OptionT+Gen.swift in Sources */, B578BCAA25385B9200B8FD42 /* Trampoline+Gen.swift in Sources */, 112097B122A7F0E6007F3D9C /* ArrayK+Gen.swift in Sources */, + B5FD894D255939F60027C335 /* PairK+Gen.swift in Sources */, 112097BF22A7FDEF007F3D9C /* Try+Gen.swift in Sources */, 112097D222A8FFC0007F3D9C /* Moore+Gen.swift in Sources */, 112097CC22A819E9007F3D9C /* Day+Gen.swift in Sources */, @@ -3317,6 +3327,7 @@ 8BA0F3E8217E2DEF00969984 /* BooleanFunctionsTest.swift in Sources */, B58B9BE925481DB4008BA4A7 /* YonedaTest.swift in Sources */, 8BA0F3FD217E2DEF00969984 /* CurryTest.swift in Sources */, + B5FD89342559382A0027C335 /* PairKTest.swift in Sources */, 119D053323D9E8B400F0D559 /* PairingTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3394,6 +3405,7 @@ 115488CF23BF9FEF00A6DE92 /* ComonadTrans.swift in Sources */, 8BA0F5A3217E2E9200969984 /* Moore.swift in Sources */, 110DE5CC23ACE29D00E5DB36 /* ComonadStore.swift in Sources */, + B598643E2558768500784952 /* PairK.swift in Sources */, 606B29EA23607607002334B2 /* Divide.swift in Sources */, 8BA0F5AF217E2E9200969984 /* Try.swift in Sources */, B57738FD252F21CC003B3EBF /* Coyoneda.swift in Sources */, diff --git a/Sources/Bow/Data/PairK.swift b/Sources/Bow/Data/PairK.swift new file mode 100644 index 0000000000..a3a40af088 --- /dev/null +++ b/Sources/Bow/Data/PairK.swift @@ -0,0 +1,194 @@ +import Foundation + +/// Witness for the `PairK` data type. To be used in simulated Higher Kinded Types. +public final class ForPairK {} + +/// Partial application of the `PairK` type constructor, omitting the last parameter. +public final class PairKPartial: Kind2 {} + +/// Higher Kinded Type alias to improve readability over `Kind, A>`. +public typealias PairKOf = Kind, A> + +/// `PairK` is a product type for kinds. Represents a type where you hold both a `Kind` and a `Kind`. +public final class PairK: PairKOf { + fileprivate let run: Pair, Kind> + + /// Safe downcast. + /// + /// - Parameter fa: Value in the higher-kind form. + /// - Returns: Value cast to `PairK`. + public static func fix(_ fa: PairKOf) -> PairK { + fa as! PairK + } + + /// Initialises a `PairK` from two values. + /// + /// - Parameters: + /// - fa: Value of the first component of the pair. + /// - ga: Value of the second component of the pair. + public init(_ fa: Kind, _ ga: Kind) { + self.run = Pair(fa, ga) + } + + /// Initialises a `PairK` from a `Pair`. + /// + /// - Parameter pair: `Pair` value to initialise this `PairK`. + public init(_ pair: Pair, Kind>) { + self.run = pair + } + + /// The value of the first component of this pair. + var first: Kind { + run.first + } + + /// The value of the second component of this pair. + var second: Kind { + run.second + } +} + +/// Safe downcast. +/// +/// - Parameter fa: Value in higher-kind form. +/// - Returns: Value cast to `PairK`. +public postfix func ^ (_ fa: PairKOf) -> PairK { + PairK.fix(fa) +} + +extension PairK where F: Applicative, G: Applicative { + /// Initialises a `PairK` from the provided values lifting them to `F` and `G`. + /// + /// - Parameters: + /// - a1: Value of the first component of the pair. + /// - a2: Value of the second component of the pair. + public convenience init(_ a1: A, _ a2: A) { + self.init(F.pure(a1), G.pure(a2)) + } +} + +// MARK: Instance of EquatableK for PairK. +extension PairKPartial: EquatableK where F: EquatableK, G: EquatableK { + public static func eq(_ lhs: PairKOf, _ rhs: PairKOf) -> Bool where A : Equatable { + lhs^.first == rhs^.first && lhs^.second == rhs^.second + } +} + +// MARK: Instance of HashableK for PairK. +extension PairKPartial: HashableK where F: HashableK, G: HashableK { + public static func hash(_ fa: PairKOf, into hasher: inout Hasher) where A : Hashable { + hasher.combine(fa^.run) + } +} + +// MARK: Instance of Invariant for PairK. +extension PairKPartial: Invariant where F: Invariant, G: Invariant { + public static func imap(_ fa: PairKOf, _ f: @escaping (A) -> B, _ g: @escaping (B) -> A) -> PairKOf { + PairK(fa^.run.bimap({ F.imap($0, f, g) }, { G.imap($0, f, g) })) + } +} + +// MARK: Instance of Functor for PairK. +extension PairKPartial: Functor where F: Functor, G: Functor { + public static func map(_ fa: PairKOf, _ f: @escaping (A) -> B) -> PairKOf { + PairK(fa^.run.bimap({ F.map($0, f) }, { G.map($0, f) })) + } +} + +// MARK: Instance of Applicative for PairK. +extension PairKPartial: Applicative where F: Applicative, G: Applicative { + public static func pure(_ a: A) -> PairKOf { + PairK(a, a) + } + + public static func ap(_ ff: PairKOf B>, _ fa: PairKOf) -> PairKOf { + PairK( + F.ap(ff^.first, fa^.first), + G.ap(ff^.second, fa^.second) + ) + } +} + +// MARK: Instance of Selective for PairK +extension PairKPartial: Selective where F: Selective, G: Selective { + public static func select(_ fab: PairKOf>, _ f: PairKOf B>) -> PairKOf { + PairK( + F.select(fab^.first, f^.first), + G.select(fab^.second, f^.second) + ) + } +} + +// MARK: Instance of Monad for PairK +extension PairKPartial: Monad where F: Monad, G: Monad { + public static func flatMap(_ fa: PairKOf, _ f: @escaping (A) -> PairKOf) -> PairKOf { + PairK( + F.flatMap(fa^.first) { f($0)^.first }, + G.flatMap(fa^.second) { f($0)^.second } + ) + } + + public static func tailRecM(_ a: A, _ f: @escaping (A) -> PairKOf>) -> PairKOf { + PairK( + F.tailRecM(a, { f($0)^.first }), + G.tailRecM(a, { f($0)^.second }) + ) + } +} + +// MARK: Instance of FunctorFilter for PairK +extension PairKPartial: FunctorFilter where F: FunctorFilter, G: FunctorFilter { + public static func mapFilter(_ fa: PairKOf, _ f: @escaping (A) -> OptionOf) -> PairKOf { + PairK( + F.mapFilter(fa^.first, f), + G.mapFilter(fa^.second, f) + ) + } +} + +// MARK: Instance of MonadFilter for PairK +extension PairKPartial: MonadFilter where F: MonadFilter, G: MonadFilter { + public static func empty() -> PairKOf { + PairK(F.empty(), G.empty()) + } +} + +// MARK: Instance of SemigroupK for PairK +extension PairKPartial: SemigroupK where F: SemigroupK, G: SemigroupK { + public static func combineK( + _ x: PairKOf, + _ y: PairKOf) -> PairKOf { + PairK( + x^.first.combineK(y^.first), + x^.second.combineK(y^.second) + ) + } +} + +// MARK: Instance of MonoidK for PairK +extension PairKPartial: MonoidK where F: MonoidK, G: MonoidK { + public static func emptyK() -> PairKOf { + PairK(F.emptyK(), G.emptyK()) + } +} + +// MARK: Instance of ApplicativeError for PairK +extension PairKPartial: ApplicativeError where F: MonadError, G: MonadError, F.E == G.E { + public typealias E = F.E + + public static func raiseError(_ e: E) -> PairKOf { + PairK(F.raiseError(e), G.raiseError(e)) + } + + public static func handleErrorWith( + _ fa: PairKOf, + _ f: @escaping (E) -> PairKOf) -> PairKOf { + PairK( + fa^.first.handleErrorWith { f($0)^.first }, + fa^.second.handleErrorWith { f($0)^.second } + ) + } +} + +// MARK: Instance of MonadError for PairK +extension PairKPartial: MonadError where F: MonadError, G: MonadError, F.E == G.E {} diff --git a/Sources/Bow/Transformers/WriterT.swift b/Sources/Bow/Transformers/WriterT.swift index 027d89a359..2752197f18 100644 --- a/Sources/Bow/Transformers/WriterT.swift +++ b/Sources/Bow/Transformers/WriterT.swift @@ -425,7 +425,7 @@ extension WriterTPartial: ApplicativeError where F: MonadError, W: Monoid { } } -// MARK: Instance of MonadError for WriterT` +// MARK: Instance of MonadError for WriterT extension WriterTPartial: MonadError where F: MonadError, W: Monoid {} // MARK: Instance of MonadReader for WriterT diff --git a/Tests/BowGenerators/Data/PairK+Gen.swift b/Tests/BowGenerators/Data/PairK+Gen.swift new file mode 100644 index 0000000000..cc0a22122e --- /dev/null +++ b/Tests/BowGenerators/Data/PairK+Gen.swift @@ -0,0 +1,18 @@ +import Bow +import SwiftCheck + +// MARK: Generator for Property-based Testing + +extension PairK: Arbitrary where F: ArbitraryK, G: ArbitraryK, A: Arbitrary { + public static var arbitrary: Gen> { + Gen.from(PairKPartial.generate >>> PairK.fix) + } +} + +// MARK: Instance of ArbitraryK for PairK + +extension PairKPartial: ArbitraryK where F: ArbitraryK, G: ArbitraryK { + public static func generate() -> PairKOf { + PairK(F.generate(), G.generate()) + } +} diff --git a/Tests/BowTests/Data/PairKTest.swift b/Tests/BowTests/Data/PairKTest.swift new file mode 100644 index 0000000000..5cb976eeee --- /dev/null +++ b/Tests/BowTests/Data/PairKTest.swift @@ -0,0 +1,51 @@ +import XCTest +import BowLaws +import Bow +import BowGenerators +import SwiftCheck + +class PairKTest: XCTestCase { + func testEquatableLaws() { + EquatableKLaws, Int>.check() + } + + func testHashableLaws() { + HashableKLaws, Int>.check() + } + + func testInvariantLaws() { + InvariantLaws>.check() + } + + func testFunctorLaws() { + FunctorLaws>.check() + } + + func testApplicativeLaws() { + ApplicativeLaws>.check() + } + + func testFunctorFilterLaws() { + FunctorFilterLaws>.check() + } + + func testMonadFilterLaws() { + MonadFilterLaws>.check() + } + + func testSemigroupKLaws() { + SemigroupKLaws>.check() + } + + func testMonoidKLaws() { + SemigroupKLaws>.check() + } + + func testApplicativeErrorLaws() { + ApplicativeErrorLaws, EitherPartial>>.check() + } + + func testMonadErrorLaws() { + MonadErrorLaws, EitherPartial>>.check() + } +}