송치원 (곰튀김)
iamchiwon.github.io
Custom Operators
in Swift
Realism Programmer
Operators
basic
Basic Operators
// Assignment Operator
var num = 40
// Compound Assignment Operator
num += 2
// Arithmetic Operators
40 + 2
44 - 2
21 * 2
84 / 2
// Remainder Operators
42 % 3
// Unary (Plus/Minus) Operator
let minusNum = -num
let plusNum = +num
// Comparison Operators
minusNum == plusNum
minusNum != plusNum
minusNum > plusNum
minusNum >= plusNum
minusNum < plusNum
minusNum <= plusNum
// Logical Operators
!true
true && true
true || false
https://docs.swift.org/swift-book/LanguageGuide/BasicOperators.html
Q.
2 + 2 * 2 == 8
의 결과는?
① true ② false
Not-Support Operators
let hello = "Hello"
let world = "World"
let greeting = hello + world
let noL = greeting - "l"
let world5 = world * 5
greeting[-1] == "d"
hello[1 ... 4] == "ello"
Binary operator '-' cannot be applied to two 'String' operands
Binary operator '*' cannot be applied to operands of type 'String' and 'Int'
'subscript(_:)' is unavailable: cannot subscript String with an Int, see the…
'subscript(_:)' is unavailable: cannot subscript String with an integer range, see …
Customize Operators
1.Overload
2.Extension
3.Customize Operators
hello[1 ... 4] == "ello"
greeting[-1] == "d"
let world5 = world * 5
let noL = greeting - "l"
Operators Overload
let hello = "Hello"
let world = "World"
let greeting = hello + world
let noL = greeting - "l" // HeoWord
let world5 = world * 5 // WorldWorldWorldWorldWorld
let text = greeting - "," - " " + "!" * 2 // HelloWorld!!
func - (_ origin: String, _ removal: String) -> String {
return origin.replacingOccurrences(of: removal, with: "")
}
func * (_ origin: String, _ times: Int) -> String {
return Array(1 ... times).map { _ in origin }.reduce("", +)
}
Extensions
let hello = "Hello"
let world = "World"
let greeting = hello + world
greeting[3] // "l"
greeting[-1] // "d"
extension String {
subscript(i: Int) -> Character {
var offset = i
while offset < 0 { offset += count }
let index = self.index(startIndex, offsetBy: offset)
return self[index]
}
}
Extensions
let hello = "Hello"
let world = "World"
let greeting = hello + world
hello[1 ... 4] // "ello"
hello[1 ..< 4] // "ell"
extension String {
subscript(_ range: CountableClosedRange<Int>) -> Substring {
let startIndex = index(self.startIndex, offsetBy: range.lowerBound)
let endIndex = index(self.startIndex, offsetBy: range.upperBound)
return self[startIndex ... endIndex]
}
subscript(_ r: CountableRange<Int>) -> Substring {
let startIndex = index(self.startIndex, offsetBy: r.lowerBound)
let endIndex = index(self.startIndex, offsetBy: r.upperBound)
return self[startIndex ..< endIndex]
}
}
Operators
custom
Custom Operator
let gray = UIView()
gray.translatesAutoresizingMaskIntoConstraints = false
gray.backgroundColor = .systemGray
view.addSubview(gray)
gray ~> view
infix operator ~>: LogicalDisjunctionPrecedence
@discardableResult
func ~> (left: UIView,
right: UIView) -> UIView {
left.topAnchor.constraint(equalTo: right.topAnchor).isActive = true
left.leftAnchor.constraint(equalTo: right.leftAnchor).isActive = true
left.rightAnchor.constraint(equalTo: right.rightAnchor).isActive = true
left.bottomAnchor.constraint(equalTo: right.bottomAnchor).isActive = true
return left
}
Precedence Groups
Group Name Associativity Example Priority
BitwiseShiftPrecedence none << >>
Higher
Lower
MultiplicationPrecedence left * /
AdditionPrecedence left + -
RangeFormationPrecedence none … ..<
CastingPrecedence none as
NilCoalescingPrecedence right ??
ComparisonPrecedence none < <= > >= ==
LogicalConjunctionPrecedence left &&
LogicalDisjunctionPrecedence left ||
TernaryPrecedence right ? :
AssignmentPrecedence right += = *= -= /=
https://developer.apple.com/documentation/swift/swift_standard_library/operator_declarations
Custom Operator
let green = UIView()
green.translatesAutoresizingMaskIntoConstraints = false
green.backgroundColor = .systemGreen
view.addSubview(green)
green.topAnchor ~> view.topAnchor + 50
green.centerXAnchor ~> view.centerXAnchor
green.widthAnchor ~> 300
green.heightAnchor ~> 200
@discardableResult
func ~> (left: NSLayoutXAxisAnchor,
right: NSLayoutXAxisAnchor) -> NSLayoutXAxisAnchor {
left.constraint(equalTo: right).isActive = true
return left
}
@discardableResult
func ~> (left: NSLayoutDimension,
right: CGFloat) -> NSLayoutDimension {
left.constraint(equalToConstant: right).isActive = true
return left
}
Custom Operator
let green = UIView()
green.translatesAutoresizingMaskIntoConstraints = false
green.backgroundColor = .systemGreen
view.addSubview(green)
green.topAnchor ~> view.topAnchor + 50
green.centerXAnchor ~> view.centerXAnchor
green.widthAnchor ~> 300
green.heightAnchor ~> 200
@discardableResult
func ~> (left: NSLayoutXAxisAnchor,
right: (anchor: NSLayoutXAxisAnchor,
constant: CGFloat)) -> NSLayoutXAxisAnchor {
left.constraint(equalTo: right.anchor,
constant: right.constant).isActive = true
return left
}
func + (left: NSLayoutXAxisAnchor,
right: CGFloat) -> (anchor: NSLayoutXAxisAnchor,
constant: CGFloat) {
(left, right)
}
Operating on functions
func uppercased(_ text: String) -> String {
return text.uppercased()
}
func output(_ it: Any) -> Void {
print("(it)")
}
output(uppercased("Hello World")) // HELLO WORLD
func ~> <A, B, C>(_ f1: @escaping (A) -> B,
_ f2: @escaping (B) -> C) -> (A) -> C {
{ a in
f2(f1(a))
}
}
(uppercased ~> output)("Hello World") // HELLO WORLD
let upperPrinter = uppercased ~> output // (String) -> Void
upperPrinter("Hello World") // HELLO WORLD
Composition of functions
func just<T>(_ f: @escaping () -> T) -> () -> T {
{
f()
}
}
func map<T, U>(_ setter: @escaping (T) -> U) -> (T) -> U {
{ t in
return setter(t)
}
}
func output(_ it: Any) -> Void {
print("(it)")
}
output(
map({ $0.uppercased() })(
just({ "Hello World" })()
)
)
// HELLO WORLD
Composition of functions
func just<T>(_ f: @escaping () -> T) -> () -> T {
{
f()
}
}
func map<T, U>(_ setter: @escaping (T) -> U) -> (T) -> U {
{ t in
return setter(t)
}
}
func output(_ it: Any) -> Void {
print("(it)")
}
let fn = just { "Hello World" } ~> map { $0.count } ~> output
fn(())
// 11
() -> String (String) -> Int (Any) -> ()
(()) -> ()
Composition of functions
@discardableResult
func run<T>(_ f: (()) -> T) -> T {
f(())
}
run(
just { "let us: Go! 2019" }
~> map { $0.uppercased() }
~> output
)
// LET US: GO! 2019
Q.
다음 중 custom operator 로 사용할 수 있는 것은?
① infix operator ~>: AdditionPrecedence
② infix operator ->: AdditionPrecedence
③ infix operator as: AdditionPrecedence
④ infix operator $$: AdditionPrecedence
https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID418
All together
readable code
run(
just { UIView() }
~> addSubView(view)
~> map { v -> UIView in
v.backgroundColor = .systemGray
v ~> self.view
return v
}
)
let green = run(
just { UIView() }
~> addSubView(view)
~> map { v -> UIView in
v.backgroundColor = .systemGreen
return v
}
~> map { v -> UIView in
v.topAnchor ~> self.view.topAnchor + 50
v.leftAnchor ~> self.view.leftAnchor + 50
v.rightAnchor ~> self.view.rightAnchor - 50
v.heightAnchor ~> 200
return v
}
)
let yellow = run(
just { UIView() }
~> addSubView(view)
~> map { v -> UIView in
v.backgroundColor = .systemYellow
return v
}
~> map { v -> UIView in
v.topAnchor ~> green.topAnchor + 100
v.centerXAnchor ~> self.view.centerXAnchor
v.widthAnchor ~> 200
v.heightAnchor ~> 200
return v
}
)
run(
just { UIView() }
~> addSubView(view)
~> map { v -> UIView in
v.backgroundColor = .systemRed
return v
}
~> map { v -> UIView in
v.centerXAnchor ~> yellow.centerXAnchor
v.centerYAnchor ~> yellow.centerYAnchor
v.widthAnchor ~> yellow.widthAnchor * 0.5
v.heightAnchor ~> yellow.heightAnchor / 2
return v
}
)
let addSubView: (UIView) -> (UIView) -> UIView = { parent in
{ v in
parent.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
return v
}
}
@discardableResult
func ~> (left: UIView,
right: UIView) -> UIView {
left.topAnchor.constraint(equalTo: right.topAnchor).isActive = true
left.leftAnchor.constraint(equalTo: right.leftAnchor).isActive = true
left.rightAnchor.constraint(equalTo: right.rightAnchor).isActive = true
left.bottomAnchor.constraint(equalTo: right.bottomAnchor).isActive = true
return left
}
@discardableResult
func ~> (left: NSLayoutDimension,
right: CGFloat) -> NSLayoutDimension {
left.constraint(equalToConstant: right).isActive = true
return left
}
@discardableResult
func ~> (left: NSLayoutXAxisAnchor,
right: NSLayoutXAxisAnchor) -> NSLayoutXAxisAnchor {
left.constraint(equalTo: right).isActive = true
return left
}
@discardableResult
func ~> (left: NSLayoutXAxisAnchor,
right: (anchor: NSLayoutXAxisAnchor,
constant: CGFloat)) -> NSLayoutXAxisAnchor {
left.constraint(equalTo: right.anchor,
constant: right.constant).isActive = true
return left
}
func + (left: NSLayoutXAxisAnchor,
right: CGFloat) -> (anchor: NSLayoutXAxisAnchor,
constant: CGFloat) {
(left, right)
}
func - (left: NSLayoutXAxisAnchor,
right: CGFloat) -> (anchor: NSLayoutXAxisAnchor,
constant: CGFloat) {
(left, -right)
}
@discardableResult
func ~> (left: NSLayoutDimension,
right: CGFloat) -> NSLayoutDimension {
left.constraint(equalToConstant: right).isActive = true
return left
}
@discardableResult
func ~> (left: NSLayoutDimension,
right: NSLayoutDimension) -> NSLayoutDimension {
left.constraint(equalTo: right, multiplier: 1).isActive = true
return left
}
@discardableResult
func ~> (left: NSLayoutDimension,
right: (anchor: NSLayoutDimension,
multiplier: CGFloat)) -> NSLayoutDimension {
left.constraint(equalTo: right.anchor,
multiplier: right.multiplier).isActive = true
return left
}
func * (left: NSLayoutDimension,
right: CGFloat) -> (anchor: NSLayoutDimension,
multiplier: CGFloat) {
(left, right)
}
func / (left: NSLayoutDimension,
right: CGFloat) -> (anchor: NSLayoutDimension,
multiplier: CGFloat) {
(left, 1 / right)
}
https://github.com/iamchiwon/CustomizeOperators
Sample Codes
Is it useful?
ObjectMapper
https://github.com/tristanhimmelman/ObjectMapper
Cartography
https://github.com/robb/Cartography
Summary
1. Operator Overloading, Extension, Custom Operator
을 활용해서 기능을 추가할 수 있다.
2. Operator는 Precedence 에 의한 연산 우선순위가 적용된다.
3. value 뿐 아니라 function 에도 연산자를 적용할 수 있다.
4. 복잡할 수 있는 연산을 간결하게 표현할 수 있다.
5. 코드의 가독성을 높일 수 있다.
( ⚠ 과하면 오히려 가독성을 해칠 수 있다 )
끝

20191116 custom operators in swift

  • 1.
  • 2.
  • 3.
    Basic Operators // AssignmentOperator var num = 40 // Compound Assignment Operator num += 2 // Arithmetic Operators 40 + 2 44 - 2 21 * 2 84 / 2 // Remainder Operators 42 % 3 // Unary (Plus/Minus) Operator let minusNum = -num let plusNum = +num // Comparison Operators minusNum == plusNum minusNum != plusNum minusNum > plusNum minusNum >= plusNum minusNum < plusNum minusNum <= plusNum // Logical Operators !true true && true true || false https://docs.swift.org/swift-book/LanguageGuide/BasicOperators.html
  • 4.
    Q. 2 + 2* 2 == 8 의 결과는? ① true ② false
  • 5.
    Not-Support Operators let hello= "Hello" let world = "World" let greeting = hello + world let noL = greeting - "l" let world5 = world * 5 greeting[-1] == "d" hello[1 ... 4] == "ello" Binary operator '-' cannot be applied to two 'String' operands Binary operator '*' cannot be applied to operands of type 'String' and 'Int' 'subscript(_:)' is unavailable: cannot subscript String with an Int, see the… 'subscript(_:)' is unavailable: cannot subscript String with an integer range, see …
  • 6.
    Customize Operators 1.Overload 2.Extension 3.Customize Operators hello[1... 4] == "ello" greeting[-1] == "d" let world5 = world * 5 let noL = greeting - "l"
  • 7.
    Operators Overload let hello= "Hello" let world = "World" let greeting = hello + world let noL = greeting - "l" // HeoWord let world5 = world * 5 // WorldWorldWorldWorldWorld let text = greeting - "," - " " + "!" * 2 // HelloWorld!! func - (_ origin: String, _ removal: String) -> String { return origin.replacingOccurrences(of: removal, with: "") } func * (_ origin: String, _ times: Int) -> String { return Array(1 ... times).map { _ in origin }.reduce("", +) }
  • 8.
    Extensions let hello ="Hello" let world = "World" let greeting = hello + world greeting[3] // "l" greeting[-1] // "d" extension String { subscript(i: Int) -> Character { var offset = i while offset < 0 { offset += count } let index = self.index(startIndex, offsetBy: offset) return self[index] } }
  • 9.
    Extensions let hello ="Hello" let world = "World" let greeting = hello + world hello[1 ... 4] // "ello" hello[1 ..< 4] // "ell" extension String { subscript(_ range: CountableClosedRange<Int>) -> Substring { let startIndex = index(self.startIndex, offsetBy: range.lowerBound) let endIndex = index(self.startIndex, offsetBy: range.upperBound) return self[startIndex ... endIndex] } subscript(_ r: CountableRange<Int>) -> Substring { let startIndex = index(self.startIndex, offsetBy: r.lowerBound) let endIndex = index(self.startIndex, offsetBy: r.upperBound) return self[startIndex ..< endIndex] } }
  • 10.
  • 11.
    Custom Operator let gray= UIView() gray.translatesAutoresizingMaskIntoConstraints = false gray.backgroundColor = .systemGray view.addSubview(gray) gray ~> view infix operator ~>: LogicalDisjunctionPrecedence @discardableResult func ~> (left: UIView, right: UIView) -> UIView { left.topAnchor.constraint(equalTo: right.topAnchor).isActive = true left.leftAnchor.constraint(equalTo: right.leftAnchor).isActive = true left.rightAnchor.constraint(equalTo: right.rightAnchor).isActive = true left.bottomAnchor.constraint(equalTo: right.bottomAnchor).isActive = true return left }
  • 12.
    Precedence Groups Group NameAssociativity Example Priority BitwiseShiftPrecedence none << >> Higher Lower MultiplicationPrecedence left * / AdditionPrecedence left + - RangeFormationPrecedence none … ..< CastingPrecedence none as NilCoalescingPrecedence right ?? ComparisonPrecedence none < <= > >= == LogicalConjunctionPrecedence left && LogicalDisjunctionPrecedence left || TernaryPrecedence right ? : AssignmentPrecedence right += = *= -= /= https://developer.apple.com/documentation/swift/swift_standard_library/operator_declarations
  • 13.
    Custom Operator let green= UIView() green.translatesAutoresizingMaskIntoConstraints = false green.backgroundColor = .systemGreen view.addSubview(green) green.topAnchor ~> view.topAnchor + 50 green.centerXAnchor ~> view.centerXAnchor green.widthAnchor ~> 300 green.heightAnchor ~> 200 @discardableResult func ~> (left: NSLayoutXAxisAnchor, right: NSLayoutXAxisAnchor) -> NSLayoutXAxisAnchor { left.constraint(equalTo: right).isActive = true return left } @discardableResult func ~> (left: NSLayoutDimension, right: CGFloat) -> NSLayoutDimension { left.constraint(equalToConstant: right).isActive = true return left }
  • 14.
    Custom Operator let green= UIView() green.translatesAutoresizingMaskIntoConstraints = false green.backgroundColor = .systemGreen view.addSubview(green) green.topAnchor ~> view.topAnchor + 50 green.centerXAnchor ~> view.centerXAnchor green.widthAnchor ~> 300 green.heightAnchor ~> 200 @discardableResult func ~> (left: NSLayoutXAxisAnchor, right: (anchor: NSLayoutXAxisAnchor, constant: CGFloat)) -> NSLayoutXAxisAnchor { left.constraint(equalTo: right.anchor, constant: right.constant).isActive = true return left } func + (left: NSLayoutXAxisAnchor, right: CGFloat) -> (anchor: NSLayoutXAxisAnchor, constant: CGFloat) { (left, right) }
  • 15.
    Operating on functions funcuppercased(_ text: String) -> String { return text.uppercased() } func output(_ it: Any) -> Void { print("(it)") } output(uppercased("Hello World")) // HELLO WORLD func ~> <A, B, C>(_ f1: @escaping (A) -> B, _ f2: @escaping (B) -> C) -> (A) -> C { { a in f2(f1(a)) } } (uppercased ~> output)("Hello World") // HELLO WORLD let upperPrinter = uppercased ~> output // (String) -> Void upperPrinter("Hello World") // HELLO WORLD
  • 16.
    Composition of functions funcjust<T>(_ f: @escaping () -> T) -> () -> T { { f() } } func map<T, U>(_ setter: @escaping (T) -> U) -> (T) -> U { { t in return setter(t) } } func output(_ it: Any) -> Void { print("(it)") } output( map({ $0.uppercased() })( just({ "Hello World" })() ) ) // HELLO WORLD
  • 17.
    Composition of functions funcjust<T>(_ f: @escaping () -> T) -> () -> T { { f() } } func map<T, U>(_ setter: @escaping (T) -> U) -> (T) -> U { { t in return setter(t) } } func output(_ it: Any) -> Void { print("(it)") } let fn = just { "Hello World" } ~> map { $0.count } ~> output fn(()) // 11 () -> String (String) -> Int (Any) -> () (()) -> ()
  • 18.
    Composition of functions @discardableResult funcrun<T>(_ f: (()) -> T) -> T { f(()) } run( just { "let us: Go! 2019" } ~> map { $0.uppercased() } ~> output ) // LET US: GO! 2019
  • 19.
    Q. 다음 중 customoperator 로 사용할 수 있는 것은? ① infix operator ~>: AdditionPrecedence ② infix operator ->: AdditionPrecedence ③ infix operator as: AdditionPrecedence ④ infix operator $$: AdditionPrecedence
  • 20.
  • 21.
  • 22.
    run( just { UIView()} ~> addSubView(view) ~> map { v -> UIView in v.backgroundColor = .systemGray v ~> self.view return v } ) let green = run( just { UIView() } ~> addSubView(view) ~> map { v -> UIView in v.backgroundColor = .systemGreen return v } ~> map { v -> UIView in v.topAnchor ~> self.view.topAnchor + 50 v.leftAnchor ~> self.view.leftAnchor + 50 v.rightAnchor ~> self.view.rightAnchor - 50 v.heightAnchor ~> 200 return v } )
  • 23.
    let yellow =run( just { UIView() } ~> addSubView(view) ~> map { v -> UIView in v.backgroundColor = .systemYellow return v } ~> map { v -> UIView in v.topAnchor ~> green.topAnchor + 100 v.centerXAnchor ~> self.view.centerXAnchor v.widthAnchor ~> 200 v.heightAnchor ~> 200 return v } ) run( just { UIView() } ~> addSubView(view) ~> map { v -> UIView in v.backgroundColor = .systemRed return v } ~> map { v -> UIView in v.centerXAnchor ~> yellow.centerXAnchor v.centerYAnchor ~> yellow.centerYAnchor v.widthAnchor ~> yellow.widthAnchor * 0.5 v.heightAnchor ~> yellow.heightAnchor / 2 return v } )
  • 24.
    let addSubView: (UIView)-> (UIView) -> UIView = { parent in { v in parent.addSubview(v) v.translatesAutoresizingMaskIntoConstraints = false return v } } @discardableResult func ~> (left: UIView, right: UIView) -> UIView { left.topAnchor.constraint(equalTo: right.topAnchor).isActive = true left.leftAnchor.constraint(equalTo: right.leftAnchor).isActive = true left.rightAnchor.constraint(equalTo: right.rightAnchor).isActive = true left.bottomAnchor.constraint(equalTo: right.bottomAnchor).isActive = true return left } @discardableResult func ~> (left: NSLayoutDimension, right: CGFloat) -> NSLayoutDimension { left.constraint(equalToConstant: right).isActive = true return left }
  • 25.
    @discardableResult func ~> (left:NSLayoutXAxisAnchor, right: NSLayoutXAxisAnchor) -> NSLayoutXAxisAnchor { left.constraint(equalTo: right).isActive = true return left } @discardableResult func ~> (left: NSLayoutXAxisAnchor, right: (anchor: NSLayoutXAxisAnchor, constant: CGFloat)) -> NSLayoutXAxisAnchor { left.constraint(equalTo: right.anchor, constant: right.constant).isActive = true return left } func + (left: NSLayoutXAxisAnchor, right: CGFloat) -> (anchor: NSLayoutXAxisAnchor, constant: CGFloat) { (left, right) } func - (left: NSLayoutXAxisAnchor, right: CGFloat) -> (anchor: NSLayoutXAxisAnchor, constant: CGFloat) { (left, -right) }
  • 26.
    @discardableResult func ~> (left:NSLayoutDimension, right: CGFloat) -> NSLayoutDimension { left.constraint(equalToConstant: right).isActive = true return left } @discardableResult func ~> (left: NSLayoutDimension, right: NSLayoutDimension) -> NSLayoutDimension { left.constraint(equalTo: right, multiplier: 1).isActive = true return left } @discardableResult func ~> (left: NSLayoutDimension, right: (anchor: NSLayoutDimension, multiplier: CGFloat)) -> NSLayoutDimension { left.constraint(equalTo: right.anchor, multiplier: right.multiplier).isActive = true return left } func * (left: NSLayoutDimension, right: CGFloat) -> (anchor: NSLayoutDimension, multiplier: CGFloat) { (left, right) } func / (left: NSLayoutDimension, right: CGFloat) -> (anchor: NSLayoutDimension, multiplier: CGFloat) { (left, 1 / right) }
  • 27.
  • 28.
  • 29.
    Summary 1. Operator Overloading,Extension, Custom Operator 을 활용해서 기능을 추가할 수 있다. 2. Operator는 Precedence 에 의한 연산 우선순위가 적용된다. 3. value 뿐 아니라 function 에도 연산자를 적용할 수 있다. 4. 복잡할 수 있는 연산을 간결하게 표현할 수 있다. 5. 코드의 가독성을 높일 수 있다. ( ⚠ 과하면 오히려 가독성을 해칠 수 있다 )
  • 30.