Generics and Functional programming in Swift
To make it a bit easier I have changed the method signatures slightly and assume that it is good enough to work for func sum_ <T> (term: (T -> T), a: T, next: (T -> T), b: T) -> T {
, where T is some kind of number.
Unfortunately there is no Number
type in Swift, so we need to make our own. Our type needs to support
- addition
- comparison
0
(the neutral element for addition)
New Protocols for the requirements of the function
Comparison is handled in the Comparable
protocol, for the reset we can create our own protocols:
protocol NeutralAdditionElementProvider {
class func neutralAdditionElement () -> Self
}
protocol Addable {
func + (lhs: Self, rhs: Self) -> Self
}
sum
implementation
We can now implement the sum
function:
func sum <T where T:Addable, T:NeutralAdditionElementProvider, T:Comparable> (term: (T -> T), a: T, next: (T -> T), b: T) -> T {
if a > b {
return T.neutralAdditionElement()
}
return term(a) + sum(term, next(a), next, b)
}
Making Int
and Double
conform to the protocols
+
is already implemented for Double
and Int
so protocol conformance is easy:
extension Double: Addable {}
extension Int: Addable {}
Providing a neutral element:
extension Int: NeutralAdditionElementProvider {
static func neutralAdditionElement() -> Int {
return 0
}
}
extension Double: NeutralAdditionElementProvider {
static func neutralAdditionElement() -> Double {
return 0.0
}
}
How to write a generic apply() function in Swift?
The HasApply protocol
First of all lets define the HasApply
protocol
protocol HasApply { }
and related extension
extension HasApply {
func apply(closure:(Self) -> ()) -> Self {
closure(self)
return self
}
}
Next let make NSObject
conform to HasApply
.
extension NSObject: HasApply { }
That's it
Let's test it
let button = UIButton().apply {
$0.titleLabel?.text = "Tap me"
}
print(button.titleLabel?.text) // Optional("Tap me")
Considerations
I wouldn't use
NSObject
(it's part of the Objective-C way of doing things and I assume it will be removed at some point in the future). I would prefer something likeUIView
instead.
extension UIView: HasApply { }
Defining a generic unfoldr function
Your sequence doesn't seem to be a UnfoldFirstSequence
. Your sequence seems to have a state B
, and f
is responsible for producing a new state and an element for the sequence. An UnfoldFirstSequence
has no state that you can control. You can only produce the next element from the previous element.
Your sequence can be modelled by the more general UnfoldSequence
, which has a State
generic parameter. In fact, an UnfoldFirstSequence<T>
is just an UnfoldSequence<T, (T?, Bool)>
! See why the former is a special case of the latter by reading the source code :)
You can create such a sequence using sequence(state:next:)
.
func unfoldr<A, B>(_ f: @escaping (B) -> (A, B)?) -> (B) -> UnfoldSequence<A, B> {
return {
sequence(state: $0) { x in
guard let (a, b) = f(x) else {
return nil
}
x = b
return a
}
}
}
Example:
let seq = unfoldr { x -> (String, Int)? in
if x == 10 {
return nil
} else {
return ("\(x)", x + 1)
}
}
seq(0).forEach { print($0) }
find() using Functional Programming
The minimum change required to make this work would be to make T conform to Equatable and use the == operator.
func findInGenericIndexedList<T:Equatable>(indexedList: [(index: Int, value: T)], element: (index: Int, value: T)) -> Int? {
let found = indexedList.filter {
element.value == $0.value
}
if let definiteFound = found.first {
return definiteFound.index
}
return nil
}
It doesn't really make sense to use === here because are usually going to be applying this to value types (especially if you are following functional paradigms) for which this is never true.
Beyond this I spent some time thinking about the problem and here is what I would do:
extension Array where Element : Equatable {
func find(element:Array.Generator.Element) -> Int? {
let indexedList = lazy(self.enumerate())
let found = indexedList.filter {
element == $1
}
let definiteFound = found.prefix(1)
return definiteFound.generate().next()?.index
}
}
Protocol extension on Array because it makes the syntax neater, lazy sequence to avoid checking every element, 0 indexed.
Diffrence between Function and Generic Function in swift
Generic functions let you use the type safety of Swift on both the parameters and the result of the function to write safer, cleaner code. For example, your first function requires that both parameters passed in be of the same type, and guarantees a return value of that same type:
let minInt: Int = simpleMin(5, 12)
let minDouble: Double = simpleMin(5.0, 12.0)
whereas your second function makes no such requirements and no such guarantee:
let minOne: AnyObject = sampleMin(5, 12.0) // minOne is an AnyObject holding an Int
let minTwo: AnyObject = sampleMin(5.0, 12) // minTwo is an AnyObject holding an Double
let minThree: AnyObject = sampleMin("Five", true) // what is supposed to happen here, exactly?
With these AnyObject
results I would need to do extra checks to make sure I understand what the function returned, since AnyObject
is (obviously) much less specific than my original parameters.
Moreover, generic functions allow you to put constraints on the parameters they accept, so you can make sure that the function is called only with arguments that make sense. Your first function requires that the parameters conform to the Comparable
protocol, meaning that I can't just call it with two random objects. The compiler will let me call your second function with two instances of UIView
, for example, and there won't be any problem until the crash when that code is executed.
Understanding protocols is an important part of using generics. A protocol defines some specific functionality that would be useful across a whole range of classes, and leaves the implementation of that functionality up to the classes themselves. The Comparable
protocol above is one example; here's the definition:
protocol Comparable : Equatable {
func <=(lhs: Self, rhs: Self) -> Bool
func >=(lhs: Self, rhs: Self) -> Bool
func >(lhs: Self, rhs: Self) -> Bool
}
This is saying that any object that "conforms to" the Comparable
protocol is going to have definitions for using the <=
, >=
, and >
operators -- that is, you'd be able to write if a > b
for any two objects that are both Comparable
.
More reading:
The Swift Programming Language: Generics
Generic Programming - Wikipedia
Generics have long been a feature of C# and Java, among other languages, so you may find more resources to help you understand their benefits and how to use them in their documentation.
How do you write a generic function to do conditional downcasting in Swift?
Generally, Swift doesn't support GenericType<A> as? GenericType<B>
cast, even if B
is a subtype of A
. Array<A> as? Array<B>
is just a exception for our convenience.
There is a undocumented internal builtin function:
func _arrayConditionalDownCastElements<SourceElement, TargetElement>(a: Array<SourceElement>) -> [TargetElement]?
With my assumption, Swift implicitly calls this function when we do someArray as? [B]
. But, with a generics type, just like your case, the Swift compiler cannot bind that to _arrayConditionalDownCastElements
because it's unpredictable in compile time.
Anyway, you can call it manually, and implement cast
as overloaded function:
func cast<T,U>(x:T, _: U.Type) -> U? {
return x as? U
}
func cast<T,U>(x:[T], _: [U].Type) -> [U]? {
return _arrayConditionalCast(x)
}
Similarly, there is _dictionaryDownCastConditional
for Dictionary
and _setDownCastConditional
for Set
:
func _dictionaryDownCastConditional<BaseKey, BaseValue, DerivedKey, DerivedValue>(source: Dictionary<BaseKey, BaseValue>) -> Dictionary<DerivedKey, DerivedValue>?
func _setDownCastConditional<BaseValue, DerivedValue>(source: Set<BaseValue>) -> Set<DerivedValue>?
Using this:
func cast<TKey,TValue, UKey, UValue>(x:[TKey: TValue], _: [UKey:UValue].Type) -> [UKey: UValue]? {
return _dictionaryDownCastConditional(x)
}
func cast<T, U>(x: Set<T>, _: Set<U>.Type) -> Set<U>? {
return _setDownCastConditional(x)
}
Again, they are undocumented. use them at your own risk :)
Related Topics
No Designated Init for Skshapenode(Circleofradius: Radius)
Change Width of a Uibarbuttonitem in a Uinavigationbar in Swift
How to Delete Object in Array of Dictionaries Using Key Value
Why I Can Not Use Equatable Function in My View When the View Can Use It in Swiftui
Hittest Prints Ar Entity Name Even When I am Not Tapping on It
Swiftui - Make Toolbar's Navigationlink Use Detail View
Crash When Running on Device After Second Launch
Take a Full Screenshot for All Webview in Swift
Comparing Two Enum Variables Regardless of Their Associated Values
Convert [(Key: String, Value: String)] in [String:String]
Swift 2: Invalid Conversion from Throwing Function of Type to Non-Throwing Function
Cloudkit - "Invalid Bundle Id for Container"
Encoding Swift String as Escaped Unicode
Cannot Invoke Initializer for Type 'Sqlite3_Destructor_Type'
How to Compare Range<String.Index> and Defaultbidirectionalindices<String.Characterview>
Tvos Textfield Transparent Background
Hovering a Modelentity in Front of Arcamera
Swift 1.2 Not Working with Same Function Name and Different Parameter