スウィフトと変異構造体


96

Swiftでの値の型の変更に関して、私が完全には理解していないことがあります。

「The Swift Programming Language」のiBookには、次の ように記載されています。デフォルトでは、値タイプのプロパティは、そのインスタンスメソッド内からは変更できません。

そして、これを可能にするために、mutating構造体と列挙型の内部でキーワードを使用してメソッドを宣言できます。

私には完全に明確ではありませんが、これは、構造体の外部から変数を変更することはできますが、独自のメソッドから変更することはできません。オブジェクト指向言語の場合と同様に、これは私には直観に反するようですが、通常は変数をカプセル化して、内部からしか変更できないようにします。構造体では、これは逆になります。詳しくは、次のコードスニペットをご覧ください。

struct Point {
    var x = 0, y = 0
    mutating func moveToX(x: Int, andY y:Int) { //Needs to be a mutating method in order to work
        self.x = x
        self.y = y
    }
}

var p = Point(x: 1, y: 2)
p.x = 3 //Works from outside the struct!
p.moveToX(5, andY: 5) 

構造体が自分のコンテキスト内からコンテンツを変更できないのに、他の場所でコンテンツを簡単に変更できる理由を誰かが知っていますか?

回答:


80

可変属性は、型ではなくストレージ(定数または変数)でマークされます。structには、mutableimmutableの 2つのモードがあると考えることができます。あなたは(私たちはそれを呼び出し不変ストレージに構造体の値を割り当てた場合let、一定のスウィフトに)値が不変モードとなり、あなたは価値のいずれかの状態を変更することはできません。(変更メソッドの呼び出しを含む)

値が変更可能なストレージ(Swiftでは変数varまたは変数と呼ばれます)に割り当てられている場合、それらの状態を自由に変更でき、変更メソッドの呼び出しが許可されます。

さらに、クラスにはこの不変/可変モードはありません。IMO、これはクラスが通常参照可能なエンティティを表すために使用されるためです。また、参照可能なエンティティは、エンティティの参照グラフを適切なパフォーマンスで不変の方法で作成および管理することが非常に難しいため、通常は変更可能です。彼らは後でこの機能を追加するかもしれませんが、少なくとも今はそうではありません。

Objective-Cプログラマーにとって、可変/不変の概念は非常によく知られています。Objective-Cでは、コンセプトごとに2つの別々のクラスがありましたが、Swiftでは、1つの構造体でこれを行うことができます。半分仕事。

C / C ++プログラマーにとっても、これは非常によく知られた概念です。これはconst、C / C ++でキーワードが行うこととまったく同じです。

また、不変値は非常に適切に最適化できます。理論的には、Swiftコンパイラー(またはLLVM)はlet、C ++の場合と同様に、によって渡される値に対してコピー削除を実行できます。不変の構造体を賢く使用すると、refcountedクラスよりもパフォーマンスが向上します。

更新

@Josephはこれは理由を提供しないと主張したので、私はもう少し追加しています。

構造体には2種類のメソッドがあります。プレーン変化するメソッド。プレーンメソッドは、不変(または非変異)を意味します。この分離は、不変のセマンティクスをサポートするためにのみ存在します。不変モードのオブジェクトは、状態をまったく変更すべきではありません。

次に、不変メソッドは、このセマンティックの不変性を保証する必要があります。つまり、内部値を変更してはなりません。そのため、コンパイラーは不変のメソッドでの自身の状態変更を許可しません。対照的に、変異メソッドは自由に状態を変更できます。

そして、なぜ不変がデフォルトであるのかという疑問を持つかもしれませんか?これは、変化する値の将来の状態を予測することが非常に困難であり、それが通常、頭痛やバグの主な原因になるためです。多くの人々は、ソリューションが変更可能なものを回避し、デフォルト不変であることがC / C ++ファミリー言語とその派生において何十年もの間、希望リストのトップにあったことに同意しました。

詳細については、純粋に機能的なスタイルを参照してください。とにかく、不変のものにはいくつかの弱点があり、それらについて議論することはトピックの外にあるため、私たちは依然として可変のものを必要としています。

これがお役に立てば幸いです。


2
だから基本的に、私はそれを次のように解釈できます:構造体は、それが可変であるか不変であるかどうかを事前に知らないので、別様に述べられていない限り、不変と見なされます。そのように見えるのは確かに理にかなっています。これは正しいです?
Mike Seghers

1
@MikeSeghers私はそれが理にかなっていると思います。
eonil 2014年

3
これはこれまでのところ2つの答えの中で最高ですが、なぜWHYに答えるとは思いませんが、デフォルトでは、値型のプロパティはそのインスタンスメソッド内から変更できません。構造体はデフォルトで不変であるため、そのことを示唆していますが、私はそれが本当だとは思いません
ジョセフナイト

4
@JosephKnight構造体のデフォルトが不変ではありません。構造体のメソッドはデフォルトで不変です。逆の仮定でC ++のように考える。C ++メソッドのデフォルトは非定数thisですconst。'constthis 'を持たせて定数インスタンスで許可する場合は、メソッドに明示的にa を追加する必要があります(インスタンスがconstの場合、通常のメソッドは使用できません)。Swiftは逆のデフォルトで同じことを行います。メソッドにはデフォルトで 'const this'があり、定数インスタンスで禁止する必要がある場合はそれ以外の場合はマークします。
アナログファイル

3
@golddoveはい、できません。すべきではない。常に新しいバージョンの値を作成または導出する必要があり、プログラムはこの方法を意図的に採用するように設計する必要があります。このような偶発的な変異を防ぐための機能があります。繰り返しますが、これは関数型プログラミングのコアコンセプトの1つです。
eonil 14

25

構造はフィールドの集合体です。特定の構造体インスタンスが変更可能な場合、そのフィールドは変更可能です。インスタンスが不変である場合、そのフィールドは不変です。したがって、特定のインスタンスのフィールドが変更可能または不変である可能性に備えて、構造タイプを準備する必要があります。

構造メソッドが基になる構造体のフィールドを変更するには、それらのフィールドが変更可能である必要があります。基礎となる構造体のフィールドを変更するメソッドが不変の構造体で呼び出されると、不変のフィールドを変更しようとします。それから良いことは何も得られないので、そのような呼び出しを禁止する必要があります。

これを実現するために、Swiftは構造メソッドを2つのカテゴリに分類します。基礎となる構造を変更し、したがって可変構造インスタンスでのみ呼び出すことができるメソッドと、基礎となる構造を変更せず、したがって可変インスタンスと不変インスタンスの両方で呼び出し可能にする必要があります。 。後者の使用法はおそらくより頻繁であり、したがってデフォルトです。

比較すると、.NETは現在(まだ!)、構造を変更する構造メソッドを変更しない構造メソッドを区別する手段を提供していません。代わりに、不変の構造体インスタンスで構造体メソッドを呼び出すと、コンパイラーは構造体インスタンスの可変コピーを作成し、メソッドに必要な処理をすべて実行させ、メソッドが完了したらコピーを破棄します。これにより、コピー操作を追加しても意味的に正しくないコードが意味的に正しいコードに変わることはほとんどありませんが、メソッドが構造を変更するかどうかに関係なく、構造体をコピーする時間をコンパイラーに浪費させます。ある意味で(「不変」値を変更する)意味的に間違っているコードを別の方法で間違っている(コードが構造を変更しているとコードに認識させる)だけです。ただし、試行された変更は破棄されます)。構造体メソッドが基になる構造を変更するかどうかを示すことを許可すると、無用なコピー操作の必要性をなくすことができ、また、誤った使用の試みにフラグが立てられるようになります。


1
.NETとの比較と、変化するキーワードがない例は、私にそれを明らかにしました。変化するキーワードが何らかの利益を生み出す理由。
Binarian、2016年

19

注意:素人の条件を先に。

この説明は、最も重要なコードレベルでは厳密には正しくありません。しかし、それは実際にSwiftに取り組んでいる男によってレビューされており、彼はそれは基本的な説明として十分に良いと言いました。

だから私は簡単かつ直接的に試してみたい、「なぜ」という質問答えます。

正確には:構造体関数を次のようにマークする必要があるのはなぜですかmutating、キーワードを変更せずに構造体パラメーターを変更できる場合に、ですか?

つまり、全体像は、Swiftを迅速に保つという哲学に大きく関係しています。

実際の物理アドレスを管理する問題のように考えることができます。あなたがあなたの住所を変えるとき、あなたの現在の住所を持っている人がたくさんいるならば、あなたは引っ越したことを彼ら全員に通知しなければなりません。しかし、誰もあなたの現在の住所を持っていない場合、あなたは好きなところに移動することができ、誰も知る必要はありません。

この状況では、Swiftは郵便局のようなものです。多くの連絡先を持つ多くの人が頻繁に移動する場合、オーバーヘッドが非常に高くなります。これらのすべての通知を処理するには、大きなスタッフを雇う必要があり、プロセスには多くの時間と労力がかかります。そのため、Swiftの理想的な状態は、町の全員ができるだけ少ない連絡先を持つことです。そうすれば、住所の変更を処理するために大きなスタッフを必要とせず、他のすべてをより速くより効率的に行うことができます。

これは、Swiftの人々がすべて、値の型と参照の型について熱狂している理由でもあります。本質的に、参照タイプは「接点」をあちこちに配置し、値タイプは通常2つ以上必要としません。値のタイプは "Swift" -erです。

したがって、小さな画像に戻ります。 structs。Swiftでは、構造体はオブジェクトが実行できるほとんどのことを実行できるため、構造体は重要ですが、値型です。

misterStruct住んでいるを想像して、物理アドレスのアナロジーを続けましょうsomeObjectVille。ここで類推は少し複雑になりますが、それでも役に立ちます。

だから、上の変数を変更するモデルにstruct、ましょうと言うには、misterStruct緑色の髪を持ち、青髪にスイッチするために取得します。アナロジーは私が言ったようにふらふらになりますが、何が起こるかというと、misterStruct老人は髪を変えるのではなく、外に出て青い髪の新しい人が入り、新しい人が自分を呼び始めますmisterStruct。誰もアドレス変更通知を受け取る必要はありませんが、誰かがそのアドレスを見ると、青い髪の男が表示されます。

では、関数をで呼び出すとどうなるかをモデル化してみましょうstruct。この場合、のようなmisterStruct注文を取得するようなものchangeYourHairBlue()です。したがって、郵便局はmisterStruct「髪の毛を青くして、終わったら教えて」という指示を出します。

彼が以前と同じルーチンに従っている場合、変数が直接変更されたときに実行したことをしている場合、misterStruct実行することは、自分の家から出て、青い髪の新しい人を呼び出すことです。しかし、それが問題です。

注文は「髪を青に変えて、いつ終わったか教えて」でしたが、その注文を受けたのは緑の男です。青い男が移動した後も、「ジョブ完了」通知を送信する必要があります。しかし、青い男はそれについて何も知りません。

[この類推を本当にひどく驚かせるために、技術的に緑髪の男に起こったことは、彼が引っ越した後、彼はすぐに自殺したということでした。だから、彼は、タスクが完了したことを誰に通知することができないのいずれか!]

この問題を回避するには、このような場合のみ、Swiftはその住所にある家に直接入り、現在の住民の髪実際に変更する必要があります。これは、新しい人を送るだけのプロセスとはまったく異なります。

そして、それがSwiftがmutatingキーワードを使用することを望んでいる理由です!

最終結果は、構造体を参照する必要があるすべてのものと同じに見えます。家の住人は青い髪をしています。しかし、それを達成するためのプロセスは実際には完全に異なります。同じことをしているように見えますが、非常に異なることをしています。これは、Swiftの構造体が一般に実行しないことを実行しています。

したがって、貧弱なコンパイラに少し助けを与え、関数structがそれ自体でこれまでにすべてのstruct関数について mutationするかどうかを判断する必要がないようにするには、同情してmutatingキーワードを使用するように求められます。

本質的に、Swiftが迅速に滞在できるようにするには、私たち全員が自分の役割を果たす必要があります。:)

編集:

私に反対票を投じたねえ、デューデット、私は私の答えを完全に書き直しました。それがあなたとよりよく座っている場合、あなたは反対投票を削除しますか?


1
これは驚くほど<f-word-here>で書かれています!だから、とてもわかりやすい。私はこれについてインターネットを精査しました、そしてここに答えがあります(まあ、とにかくソースコードを経由する必要はありません)。執筆に時間を割いていただき、ありがとうございました。
Vaibhav

1
私はそれが正しく理解されていることを確認したいだけです。SwiftでStructインスタンスのプロパティが直接変更されると、そのStructのインスタンスが「終了」し、プロパティが変更された新しいインスタンスが「作成」されます。裏では?そして、インスタンス内で変更メソッドを使用してそのインスタンスのプロパティを変更すると、この終了と再初期化のプロセスは行われませんか?
火曜日

@Teo、私は決定的に答えることができればいいのですが、私が言えるのは、実際のSwift開発者を過ぎてこのアナロジーを実行したことです。しかし、それだけではなく、広い意味でそれだけではないことを理解すれば、そうです、それがこの類推が言っていることです。
ルモットジュース

8

Swift構造体は、定数(を介してlet)または変数(を介してvar)としてインスタンス化できます

SwiftのArray構造体を考えてみてください(はい、それは構造体です)。

var petNames: [String] = ["Ruff", "Garfield", "Nemo"]
petNames.append("Harvey") // ["Ruff", "Garfield", "Nemo", "Harvey"]

let planetNames: [String] = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
planetNames.append("Pluto") //Error, sorry Pluto. No can do

アペンドが惑星名で機能しなかったのはなぜですか?追加はmutatingキーワードでマークされているためです。のでplanetNames使用して宣言されたlet、このようにマークされたすべての方法は立ち入り禁止です。

あなたの例では、コンパイラは、の外の1つ以上のプロパティに割り当てることにより、構造体を変更していることを通知できますinit。コードを少し変更すると、それが表示され、構造体の外から常にアクセスできるxyは限りません。let最初の行に注意してください。

let p = Point(x: 1, y: 2)
p.x = 3 //error
p.moveToX(5, andY: 5) //error

5

C ++との類似を考えてみましょう。mutating/ not- であるSwiftの構造体メソッドはmutating、非const/ であるC ++のメソッドに類似していconstます。constC ++でマークされたメソッドは、同様に構造体を変更できません。

構造体の外から変数を変更できますが、独自のメソッドからは変更できません。

C ++では、「構造体の外部から変数を変更する」こともできますが、非構造体変数がある場合のみconstです。const構造体変数がある場合、varに割り当てることはできず、非constメソッドを呼び出すこともできません。同様に、Swiftでは、構造体変数が定数でない場合にのみ、構造体のプロパティを変更できます。構造体定数がある場合、プロパティに割り当てることも、mutatingメソッドを呼び出すこともできません。


1

私がSwiftの学習を始めたときも同じことを考えていました。これらの答えはそれぞれ、おそらくいくつかの洞察を追加しながら、それ自体が冗長で紛らわしいものです。あなたの質問への答えは実際にはかなり簡単だと思います…

構造体の中で定義された変更メソッドは、将来作成される自分自身のすべてのインスタンスを変更する権限を必要としています。これらのインスタンスの1つが不変の定数に割り当てられた場合はletどうなりますか?ええとああ。自分から身を守るために(そしてエディターやコンパイラーに何をしようとしているのかを知らせるために)、インスタンスメソッドにこのような力を与えたいときは、明示的にする必要があります。

対照的に、構造体の外部からのプロパティの設定は、その構造体の既知のインスタンスで動作しています。定数に割り当てられている場合、Xcodeは、メソッド呼び出しで入力した瞬間にそれを通知します。

これは、私がSwiftを使い始めて、入力時にエラーが通知されるので、私がSwiftについて気に入っている点の1つです。確かに、あいまいなJavaScriptバグのトラブルシューティングは簡単です。


1

SWIFT:Structsでの変異関数の使用

Swiftプログラマーは、そのプロパティをStructメソッド内で変更できないようにStructsを開発しました。たとえば、以下のコードを確認してください

struct City
{
  var population : Int 
  func changePopulation(newpopulation : Int)
  {
      population = newpopulation //error: cannot modify property "popultion"
  }
}
  var mycity = City(population : 1000)
  mycity.changePopulation(newpopulation : 2000)

上記のコードを実行すると、Struct CityのプロパティPopulationに新しい値を割り当てようとしているため、エラーが発生します。デフォルトでは、Structsプロパティは独自のメソッド内で変更できません。これはApple開発者が作成した方法であり、デフォルトでStructsは静的な性質を持ちます。

どうすれば解決できますか?代替は何ですか?

キーワードの変更:

関数をStruct内で変更すると宣言すると、Structのプロパティを変更できます。上記のコードの5行目は、次のように変更されます。

mutating changePopulation(newpopulation : Int)

これで、newpopulationの値をメソッドのスコープ内でプロパティのpopulationに割り当てることができます。

注意 :

let mycity = City(1000)     
mycity.changePopulation(newpopulation : 2000)   //error: cannot modify property "popultion"

Structオブジェクトにvarではなくletを使用すると、プロパティの値を変更できなくなります。これが、letインスタンスを使用して変更関数を呼び出そうとするとエラーが発生する理由でもあります。したがって、プロパティの値を変更するときは常にvarを使用することをお勧めします。

あなたのコメントや考えを聞いてみたいです…..

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.