Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the ~> (tilde greater than) operator used for in Swift?

Tags:

swift

Swift 1.1 includes the declaration of the ~> operator:

infix operator ~> {
    associativity left
    precedence 255
}

What is this used for in Swift? It appears to be declared but no functions are defined that leverage it. Other developers have used it for reactive patterns and for marshaling closures between queues, but I am wondering why it's defined in the standard framework. I surmise that it's there to "reserve" a custom operator for developer use, given that it has the highest precedence possible.

like image 683
Jason Avatar asked Nov 05 '14 13:11

Jason


2 Answers

Since Swift has been open-sourced we can see the actual reason for including the ~> in stdlib: as a workaround for method specialization in a child protocol in Swift 1.x.

// Workaround for <rdar://problem/14011860> SubTLF: Default
// implementations in protocols.  Library authors should ensure
// that this operator never needs to be seen by end-users.  See
// test/Prototypes/GenericDispatch.swift for a fully documented
// example of how this operator is used, and how its use can be hidden
// from users.
infix operator ~> { associativity left precedence 255 }

A detailed example can be found in test/Prototypes/GenericDispatch.swift.

Note: Do not use ~> in Swift 2+. This is a historical workaround. It is no longer needed. Read on.


How ~> works

In Swift 2, the only remaining instance of ~> is the abs function (which is probably going away as well). We can see how actually ~> works. From stdlib/public/core/IntegerArithmetic.swift.gyb, the SignedInteger protocol defines an ~> operator:

struct _Abs {}

protocol SignedNumber: Comparable {
    prefix func -(_: Self) -> Self

    func ~> (_: Self, _: (_Abs, ()) -> Self
}

func ~> <T: SignedNumber>(x: T, _: (_Abs, ()) -> T {
    return x < 0 ? -x : x
}

Here, the RHS of the arrow ~> specifies what command can be sent to the object. Think of p~>(cmd, args) as something like p.cmd(args).

Then we provide the default implementation of _Abs when given a SignedNumber.

protocol AbsoluteValuable : SignedNumber {
    static func abs(_: Self) -> Self
}

func ~> <T: AbsoluteValuable>(x: T, _: (_Abs, ())) -> T {
    return T.abs(x)
}

Next, we have a child protocol, AbsoluteValuable, which perhaps has a more efficient way to compute the absolute value than x < 0 ? -x : x. We then specialize the ~> operator for AbsoluteValuable.

func abs<T: SignedNumber>(_ x: T) -> T {
    return x ~> (_Abs(), ())
}

Finally we hide the ~> call in a public wrapper method. If T is actually an AbsoluteValuable, the more specialized and thus more efficient ~> will be chosen.

This is also why we get 42 ~> _advance(12) or 42 ~> _distanceTo(23) as shown in @rintaro's answer, because the .advanceBy and .distanceTo methods are O(n) for a general ForwardIndexType, but can be implemented in O(1) if the type is RandomAccessIndexType.


You don't need ~>

This pattern could also be done without invoking the ~> operator, using extensions on a protocol:

protocol SignedInteger: Comparable {
    prefix func -(_: Self) -> Self

    func genericAbs() -> Self
}

extension SignedInteger {
    func genericAbs() -> Self {
        return self < 0 ? -self : self
    }
}

protocol AbsoluteValueable: SignedInteger {
    static func abs(_: Self) -> Self
}

extension AbsoluteValueable {
    func genericAbs() -> Self {
        return Self.abs(self)
    }
    // normally you would allow subtypes to override
    // genericAbs() directly, instead of overriding 
    // static abs().
}

func abs<T: SignedInteger>(x: T) -> T {
    return x.genericAbs()
}

In particular this is why all other ~> implementations besides abs are gone in Swift 2: all specializations using this technique has been changed to use protocol extensions which is more obvious, e.g.

  • underestimateCount in commit c48d6aa0 (2015 Apr 16)
  • advancedBy, distanceTo in commit 311baf73 (2015 Aug 4)
  • etc.

Note: The radar problem 14011860 is not public, but we may see the scope of the bug by its duplicates on OpenRadar:

  • #17298843 "Swift: Support protocol default methods and protocol extensions (=mixins)"
  • #17412469 "Swift - Allow adding functions with implementation to protocols"
like image 58
kennytm Avatar answered Nov 11 '22 12:11

kennytm


It seems, It's related collection/sequence/index types

According to Defines-Swift:

protocol SequenceType : _Sequence_Type {
  typealias Generator : GeneratorType
  func generate() -> Generator
  func ~>(_: Self, _: (_UnderestimateCount, ())) -> Int
  func ~><R>(_: Self, _: (_PreprocessingPass, ((Self) -> R))) -> R?
  func ~>(_: Self, _: (_CopyToNativeArrayBuffer, ())) -> _ContiguousArrayBuffer<Self.Generator.Element>
}

protocol CollectionType : _CollectionType, SequenceType {
  subscript (position: Self.Index) -> Self.Generator.Element { get }
  func ~>(_: Self, _: (_CountElements, ())) -> Self.Index.Distance
}

protocol ForwardIndexType : _ForwardIndexType {
  func ~>(start: Self, _: (_Distance, Self)) -> Self.Distance
  func ~>(start: Self, _: (_Advance, Self.Distance)) -> Self
  func ~>(start: Self, _: (_Advance, (Self.Distance, Self))) -> Self
}

protocol SignedNumberType : _SignedNumberType {
  prefix func -(x: Self) -> Self
  func ~>(_: Self, _: (_Abs, ())) -> Self
}

func ~><T : _CollectionType>(x: T, _: (_CountElements, ())) -> T.Index.Distance
func ~><T : _CollectionType>(x: T, _: (_UnderestimateCount, ())) -> Int
func ~><T : _CollectionType, R>(s: T, args: (_PreprocessingPass, ((T) -> R))) -> R?
func ~><T : _SequenceType>(s: T, _: (_UnderestimateCount, ())) -> Int
func ~><T : _SequenceType, R>(s: T, _: (_PreprocessingPass, ((T) -> R))) -> R?
func ~><S : _Sequence_Type>(source: S, _: (_CopyToNativeArrayBuffer, ())) -> _ContiguousArrayBuffer<S.Generator.Element>
func ~><C : CollectionType where C._Element == C._Element>(source: C, _: (_CopyToNativeArrayBuffer, ())) -> _ContiguousArrayBuffer<C._Element>
func ~><T>(x: EmptyCollection<T>, _: (_CountElements, ())) -> Int
func ~><T : _ForwardIndexType>(start: T, rest: (_Distance, T)) -> T.Distance
func ~><T : _ForwardIndexType>(start: T, rest: (_Advance, T.Distance)) -> T
func ~><T : _ForwardIndexType>(start: T, rest: (_Advance, (T.Distance, T))) -> T
func ~><T : _BidirectionalIndexType>(start: T, rest: (_Advance, T.Distance)) -> T
func ~><T : _BidirectionalIndexType>(start: T, rest: (_Advance, (T.Distance, T))) -> T
func ~><T : _RandomAccessIndexType>(start: T, rest: (_Distance, (T))) -> T.Distance
func ~><T : _RandomAccessIndexType>(start: T, rest: (_Advance, (T.Distance))) -> T
func ~><T : _RandomAccessIndexType>(start: T, rest: (_Advance, (T.Distance, T))) -> T
func ~><T : _SignedNumberType>(x: T, _: (_Abs, ())) -> T
func ~><T : AbsoluteValuable>(x: T, _: (_Abs, ())) -> T
func ~><T>(x: CollectionOfOne<T>, _: (_CountElements, ())) -> Int

For example,

42 ~> _advance(12) // -> 54
42 ~> _distanceTo(23) // -> -19

I don't know how these are being used, though :-/

like image 37
rintaro Avatar answered Nov 11 '22 11:11

rintaro