3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

someとany

Posted at

自分の理解

  • some: 関数やComputed Propertiesの返り値で使用。処理の中で1種類のTypeを返したい & 呼び出し元ではそのTypeのProtocolで返り値を扱いたい時に使う。
  • any: existentialである定義をわかりやすくするための書き方

someは多分packageの境界とかで使用するかもなあ。
anyもswift6から必須になるらしいので、今のうちにつけといた方が良いのかも。

なぜsome、anyがあるのか

existentialとは

existentialであるとは、参考のサイトを見る限りでは定義にProtocolが使用されているものを指すらしい。

protocol Product {
    var id: Int { get }
    var name: String { get }
    var price: Int { get }

    func compareString(product: Self) -> String
}

let product: Product // <- existential

genericsとexistentialの違い

簡単にいうと抽象化した型の推測が効くか聞かないか。

以下の例を見てみる。(あまり良い例ではない気がするので、もし他に良き例が思い付いたらそれにしたい。。。)

protocol Product {
    var id: Int { get }
    var name: String { get }
    var price: Int { get }
    
    init(id: Int, name: String, price: Int)
}

struct SmartPhone: Product {
    private(set) var id: Int
    private(set) var name: String
    private(set) var price: Int
    private(set) var isIPhone: Bool = false
    
    init(id: Int, name: String, price: Int) {
        self.id = id
        self.name = name
        self.price = price
    }
    
    mutating func setIsIPhone(_ value: Bool) {
        isIPhone = value
    }
}

func existentialProduct(product: Product) -> Product {
    return product
}

func updateProductPrice<P: Product>(product: P, price: Int) -> P {
    P.init(id: product.id, name: product.name, price: product.price)
}

let phone = SmartPhone(id: 1, name: "iphone se", price: 50000)

let existentialPhone = existentialProduct(product: phone)
existentialPhone.setIsIPhone(true)

var updatedProduct = updateProductPrice(product: phone, price: 100000)
updatedProduct.setIsIPhone(true)

productというprotocolと、それを継承したSmartPhoneというstructがある。
SmartPhoneのinstanceを作成し、それを

  • existentialProduct(product: Product) -> Product ……(1)
  • updateProductPrice(product: P, price: Int) -> P ……(2)

という命令にかけて見ている。この時、(1)の引数、返り値はexistentialである。

この際、(1)の方は返り値がそのままProductとして返ってくるが、
(2)を使用した場合にはPの型が引数のproduct: Pから推測されて、SmartPhoneとわかり、返り値の方もSmartPhoneで帰ってくる。

実際、

  • existentialPhone.setIsIPhone(true)

はエラーとなる。

existensialの型のプロパティにアクセス

func existentialProduct(product: Product) -> Product

の内部でproductのプロパティにアクセスしようとすると、extentialであり、型の推測ができず、実際の値はわからない。
なので、ランタイム時にProduct protocoのデータが格納されているメモリから、実際のSmartPhoneのデータが格納されているメモリへの間接アクセスをする必要があり、少しパフォーマンスが悪い。

対して、

updateProductPrice(product: P, price: Int) -> P

はコンパイル時に型が推測されるため、ランタイム時には実データへ直接アクセスするので、こちらの方がパフォーマンスが良い。

なので、必要がなければ、なるべくprotocolよりgenericsを使用した方が良い、ということになる。

any

こちらは簡単で、単に、existentialな定義をわかりやすくするための表現。
なので、(1)は

func existentialProduct(product: any Product) -> any Product {
    return product
}

のように書ける。

some

以下の例を通して見ていく。

protocol Product {
    var id: Int { get }
    var name: String { get }
    var price: Int { get }

    func compareString(product: Self) -> String
}

struct SmartPhone: Product {
    private(set) var id: Int
    private(set) var name: String
    private(set) var price: Int

    func compareString(product: SmartPhone) -> String {
        let nameCompare = "\(product.name) vs \(name)"
        let priceCompare = "price: \(product.price) --- \(price)"

        return "\(nameCompare) \n \(priceCompare)"
    }
}

func createSmartPhone(id: Int, name: String, price: Int) -> SmartPhone {
    SmartPhone(id: id, name: name, price: price)
}

以上の例ではSmartPhoneのinstanceを返すような関数を作成した。
仮定として、この関数がパッケージに入っていて、その外部からこの関数を利用したい、とする。
この時、SmartPhoneでは具体的なクラスがわかってしまうので、なるべくならば隠蔽したい。

パッと思いつくのは以下のような返り値をProductにしてしまうことだが、エラーが出てしまう。

func createSmartPhone(id: Int, name: String, price: Int) -> Product {
    SmartPhone(id: id, name: name, price: price)
}

なぜか。
Product protocolの関数中で、Selfを引数として使用しているから。
同様にProtocolがassociatedTypeを使用していたりすると、ここで使用することができない。
また、この様に返せたとして、返り値がexistentialであり、少しパフォーマンスが落ちる。

この様な時にsomeを使用する。

func createSmartPhone(id: Int, name: String, price: Int) -> some Product {
    SmartPhone(id: id, name: name, price: price)
}

この時、呼び出し側では「Productに準拠した何かしらの具体的なクラス」として扱われ、具体クラスを返り値とした時と変わらないパフォーマンスが出せる。また、Selfの問題も無視できる。

ただ、縛りがあり、

  • someを返り値とする関数やComputed Propertyの返り値は一意でならなければならない。

という違いがあることに注意する。

なので、この関数の中でProductに準拠したSmartPhone以外のクラスのinstanceを返すことはできない。

このように、someは「一つしか返すクラスの種類はないが、クラス情報を隠蔽したい」ときに使用する。

(推測)なぜsomeで複数個のクラスが返せないか

あくまで推測です。記事あったら教えてください。。。

比較をする際に矛盾が生じるからではないかと推測している。

複数個具体的なクラスを返せる、と仮定してみる。
some Productは何かしらの具体的であり、Productを継承したクラスを返したいが、関数自体は具体的なクラスを1つしか指定して返すことができない。

そこで、複数個のクラスを返す代わりに、Productを継承したSomeProductクラスをコンパイルしているときに作成し、代わりに返す様なことが必要になる。

let flag = true
func _createProduct(id: Int, name: String, price: Int) -> some Product {
    if flag {
        return SmartPhone(id: id, name: name, price: price)
    } else {
        return Instrument(id: id, name: name, price: price)
    }
}

といったものがあった際、コンパイルすると、

let flag = true
func _createProduct(id: Int, name: String, price: Int) -> SomeProduct {
    SomeProduct(id: id, name: name, price: price)
}

といった様に解釈される必要がある。
この時、以下の様な時にコンパイル前と矛盾が起きる。

let flag = true
func _createProduct(id: Int, name: String, price: Int) -> SomeProduct {
    SomeProduct(id: id, name: name, price: price)
/*コンパイル前
    if flag {
        return SmartPhone(id: id, name: name, price: price)
    } else {
        return Instrument(id: id, name: name, price: price)
    }
*/
}

// SmartPhoneクラスのつもり
let a = _createProduct(id: 1, name: "iPhone", price: 100000)

let flag = false
// Instrumentクラスのつもり
let b = _createProduct(id: 1, name: "iPhone", price: 100000)

// ただ、コンパイルされると、a,bのどちらもSomeProductクラスと見做されて、
// ここがTrueになってしまい、矛盾
type(of: a) == type(of: b)
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?