Instance Methods are “Curried” Functions in Swift

8930 ワード

インスタンスメソッドは、SwiftのCurried関数です.
Updates:
Jul 29,2014 Made the action property in TargetActionWrapper non-optional.target must remain optional because it’s weak.TargetActionWrapperのaction属性をオプションではありません.ターゲットは弱いので、オプションを維持する必要があります.
Nov 26,2017 Clarified my usage of the term「curried」,which isn’t strictly correct.Updated the code to Swift 4.カレーという言葉の使い方を明らかにしましたが、これは厳密ではありません.コードをSwift 4に更新します.
An instance method in Swift is just a type method that takes the instance as an argument and returns a function which will then be applied to the instance.
Swiftのインスタンスメソッドは、インスタンスをパラメータとして関数を返し、インスタンスに適用するタイプのメソッドにすぎません.
I recently learned about a Swift feature that blew my mind a little. Instance methods are just (partially) “curried” functions that take the instance as the first argument. What’s a curried function, you ask?
swiftの機能は私の頭の中に少し浮かび上がった.インスタンスメソッドは、インスタンスを最初のパラメータとする「curried」関数にすぎません.カレー関数とは何ですか?
Curryingコリー化
The idea behind currying (named after mathematician Haskell Curry) is that you any function that takes multiple parameters can be transformed into a chained series of fundtions that take one argument each.
Currying(数学者Haskell Curryによって命名された)の背後にある考え方は,複数のパラメータを持つ関数はいずれも1つの鎖が一緒にある関数(鎖関数)に変換でき,各関数には1つのパラメータがあるということである.
For example, suppose you have function of type (Int, Double) -> String — it takes an Int and a Double , and returns a String . If we curry this function, we get (Int) -> (Double) -> (String) , i.e. a function that takes just an Int and returns a second function. This second function then takes the Double argument and returns the final String .To call the curried function,you’d chain two function calls:たとえば、(Int,Double) - > Stringのタイプの関数があるとします.IntDoubleのタイプのパラメータを受け入れ、Stringの値を返します.curryという関数があれば、(Int) - >(Double) - >(String)のタイプのパラメータを受け入れて関数を返します..2番目の関数はIntパラメータを受け入れ、最終的なDoubleを返します.curried関数を呼び出すには、2つの関数呼び出しをリンクする必要があります.
let result: String = f(42)(3.14)
// f takes an Int and returns a function that takes a Double.

Why would you want to do this? The big advantage of curried functions is that they can be partially applied, i.e. some arguments can be specified (bound) before the function is ultimately called. Partial function application yields a new function that you can then e.g. pass around to another part of your code. Languages like Haskell and ML use currying for all functions.
なぜこれをしたいのですか?curried関数の大きな利点は、それらを部分的に適用できることです.つまり、最終的に関数を呼び出す前にいくつかのパラメータを指定(バインド)することができます.一部の関数アプリケーションは、コードの他の部分として渡すことができる新しい関数を生成します.HaskellやMLのような言語ではcurryingを使用してすべての機能を完了します.
Swift uses the same idea of partial application for instance methods. Although it’s not really accurate to say instance methods in Swift are “curried”, I still like to use this term.
Swiftはインスタンスメソッドの部分と同じ考えを使用します.Swiftのインスタンスメソッド「curried」は正確ではありませんが、私はこの用語を使用するのが好きです.
Example
Consider this simple example of a class that represents a bank account:
銀行口座を表すクラスの簡単な例を考慮します.
class BankAccount {
    var balance: Double = 0.0

    func deposit(_ amount: Double) {
        balance += amount
    }
}

We can obviously create an instance of this class and call the deposit() method on that instance:
このクラスのインスタンスを作成し、そのインスタンスでdeposit()メソッドを呼び出すことは明らかです.
let account = BankAccount()
account.deposit(100) // balance is now 100

So far, so simple. But we can also do this:
これまでは簡単でしたが、私たちもそうすることができます.
let depositor = BankAccount.deposit(_:)
depositor(account)(100) // balance is now 200

This is totally equivalent to the above. What’s going on here? We first assign the method to a variable. Note that we didn’t pass an argument after BankAccount.deposit(_:) — we’re not calling the method here (which would yield an error because you can’t call an instance method on the type), just referencing it, much like a function pointer in C. The second step is then to call the function stored in the depositor variable. Its type is as follows:
これは上記の内容とまったく同じです.ここで何が起こったのですか.まずメソッドを変数に割り当てます.BankAccount.deposit(:)の後にパラメータを渡していません.ここでこのメソッドを呼び出していません(インスタンスメソッドをタイプで呼び出すことができないとエラーが発生するためです).を参照します.Cの関数ポインタに似ています.2番目のステップは、変数に格納されている関数を呼び出します.そのタイプは次のとおりです.
let depositor: BankAccount -> (Double) -> ()

In other words, this function has a single argument, a BankAccount instance, and returns another function. This latter function takes a Double and returns nothing. You should recognize the signature of the deposit() instance method in this second part.
すなわち、この関数には、BankAccountインスタンスという単一のパラメータがあり、別の関数が返されます.次の関数はDoubleを使用し、何も返されません.deposit()インスタンスメソッドの署名は、第2のセクションで識別する必要があります.
I hope you can see that an instance method in Swift is simply a type method that takes the instance as an argument and returns a function which will then be applied to the instance. Of course, we can also do this in one line, which makes the relationship between type methods and instance methods even clearer:
Swiftのインスタンス・メソッドは、パラメータとしてインスタンスを返し、インスタンスに適用するタイプ・メソッドにすぎないことを確認してください.もちろん、タイプ・メソッドとインスタンス・メソッドの関係をより明確にするには、1行で実行することもできます.
BankAccount.deposit(account)(100) // balance is now 300

By passing the instance to BankAccount.deposit(), the instance gets bound to the function. In a second step, that function is then called with the other arguments. Pretty cool, eh?
インスタンスをBankAccount.deposit()に渡すと、インスタンスは関数にバインドされます.2番目のステップで、他のパラメータを使用して関数を呼び出します.クールです.うん?
Implementing Target-Action in Swift
Christoffer Lernö shows in a post on the developer forums how this characteristic of Swift’s type system can be used to implement the target-action pattern in pure Swift. In contrast to the common implementation in Cocoa, Christoffer’s solution does not rely on Objective-C’s dynamic message dispatch mechanism. And it comes with full type safety because it does not rely on selectors.
[開発者フォーラムでの投稿]https://devforums.apple.com/message/1008188#1008188)では、Swiftタイプシステムのこの特性が[ターゲット-アクションモード](https純粋なSwift://developer.apple.com/library/ios/documentation/general/conceptual/Devpedia-Cocoaapp/TargeAction.htmlを実現するためにどのように使用されるかを示しています..Cocoaの一般的な実装とは対照的に、ChristofferのソリューションはObjective-Cの動的メッセージスケジューリングメカニズムに依存しません.セレクタに依存しないため、完全なセキュリティを備えています.
This pattern is often better than using plain closures for callbacks, especially when the receiving objects has to hold on to the closure for an indeterminate amount of time. Using closures often forces the caller of the API to do extra work to prevent strong reference cycles. With the target-action pattern, the object that provides the API can do the strong-weak dance internally, which leads to cleaner code on the calling side.
このモードは通常、通常の閉パケットを使用してコールバックを行うよりも良く、特に受信対象が不確定な時間内に閉パケットを保持しなければならない場合、閉パケットを使用すると、APIの呼び出し元に強い参照期間を防止するために追加の作業を強制することが多い.ターゲット動作モードを使用すると、APIを提供する対象は、内部で強弱ダンスを実行することができ、これにより、呼び出しの面でより明確にすることができるを選択します.
For example, a String class using target-action might look like this in Swift (adopted from a dev forums post by Jens Jakob Jensen):
たとえば、target-actionを使用するControlクラスは、Swiftでこのように見える場合があります(Jens Jakob Jensenの開発フォーラムの投稿から採用されています):
Update July 29, 2014: Made the action property in TargetActionWrapper non-optional. target must be optional because it is weak.
2014年7月29日更新:TargetActionWrapperのaction属性をオプションではありません.targetは弱い参照であるため、オプションでなければなりません.
protocol TargetAction {
    func performAction()
}

struct TargetActionWrapper : TargetAction {
    weak var target: T?
    let action: (T) -> () -> ()

    func performAction() -> () {
        if let t = target {
            action(t)()
        }
    }
}

enum ControlEvent {
    case touchUpInside
    case valueChanged
    // ...
}

class Control {
    var actions = [ControlEvent: TargetAction]()

    func setTarget(_ target: T, action: @escaping (T) -> () -> (), controlEvent: ControlEvent) {
        actions[controlEvent] = TargetActionWrapper(target: target, action: action)
    }

    func removeTargetForControlEvent(controlEvent: ControlEvent) {
        actions[controlEvent] = nil
    }

    func performActionForControlEvent(controlEvent: ControlEvent) {
        actions[controlEvent]?.performAction()
    }
}

Usage:
class MyViewController {
    let button = Control()

    func viewDidLoad() {
        button.setTarget(self, action: MyViewController.onButtonTap, controlEvent: .touchUpInside)
    }

    func onButtonTap() {
        print("Button was tapped")
    }
}