Typical is a Swift micro-framework for wrapping the closure (Subject) -> Bool
into a composable form.
A closure with the form (Subject) -> Bool
is very useful for matching purposes, where Subject
is the type you want to test. Typical is a protocol that adds operators and Collection
methods so you can join multiple closures together, with custom matching logic, and create a single instance that is also in the form of (Subject) -> Bool
.
public protocol Typical {
associatedtype Subject
func test(_ subject: Subject) -> Bool
init(_ predicate: @escaping (Subject) -> Bool)
}
If you want to match instances of Int
with several test conditions that you can define in the form of (Int) -> Bool
:
{ $0 == 8 }
{ $0 > 5 }
{ $0 < 15 }
First create a type that can store the closure in a variable and implement Typical
:
struct TypicalInt: Typical {
typealias Subject = Int
private var closure: (Int) -> Bool
init(_ test: @escaping (Int) -> Bool) {
closure = test
}
func test(_ s: Int) -> Bool {
return closure(s)
}
}
Now you can create 3 instances of your struct:
let isEqualTo8 = TypicalInt { $0 == 8 }
let isGreaterThan5 = TypicalInt { $0 > 5 }
let isLessThan15 = TypicalInt { $0 < 15 }
Since the struct implements Typical
we can join them all together to make 1 struct that combines the logic of all the matches. Here we match the int if it's greater than 5
, less than 15
, but not equal to 8
:
let selectTheRightInt = isGreaterThan5 && isLessThan15 && !isEqualTo8
Now we can test integers by using selectTheRightInt.test(6)
(returns true). Which is handy if you have an array of Int
values:
let ints = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
ints.filter(selectTheRightInt.test)
This will return [6,7,9,10,11,12,13,14]
.
The framework also has a handy class, to make implementing Typical
easy:
public final class Matching<T>: Typical {
public typealias Subject = T
private var predicate: (T) -> Bool
public init(_ test: @escaping (T) -> Bool) {
predicate = test
}
public func test(_ s: T) -> Bool {
return predicate(s)
}
}
So instead of my custom struct I could have just used:
typealias TypicalInt = Matching<Int>
Typical includes the &&
, ||
, and !
operators, as well as a withAll
and withAny
property on collections of Typical instances. There are also static methods withAll
and withAny
that take a variable list of Typical
instances, as well as a pick
method for ternary operator-style logic.
It came about because I wanted to have reusable matching logic in unit tests. I was testing instances of URLRequest
and created a bunch of operators to test the hostname, scheme, path, querystring etc. I've inlcuded some examples in the unit tests.
It was also bit of fun, since I was trying to learn Swift generics. I also think Swift micro-frameworks are pretty cool, and I wanted to write one! In reality it's a silly little protocol with limited functionalty, but who knows, someone else might find it useful too.