LoginSignup
1

More than 3 years have passed since last update.

SwiftのReverse GenericsというかOpaque Typeの紹介(社内勉強会用)

Last updated at Posted at 2020-05-27
1 / 10

Swiftとは

Wikipedia より

Swift(スウィフト)は、アップルのiOSおよびmacOS、Linuxで利用出来るプログラミング言語。Worldwide Developers Conference (WWDC) 2014で発表された。アップル製OS上で動作するアプリケーションの開発に従来から用いられていたObjective-CやObjective-C++、C言語と共存することが意図されている。

swift.org Language Guide より

Swift is a type-safe language, which means the language helps you to be clear about the types of values your code can work with.


Type-safe

var id: Int = 123
var name: String = "abc"

name = 456 // Compile error: cannot assign value of type 'Int' to type 'String

var ids: Array<Int> = []
ids.append(id)

ids.append("xyz") // Compile 

安全に、呼び出し側が指定した型で、Arrayを使用することができる (Generics)


Genericsを使わないと、

class IntStack {
    var items = [Int]()
    func push(_ item: Int) {
        items.append(item)
    }
    func pop() -> Int {
        return items.removeLast()
    }
}

var ids = IntStack()
ids.push(id)
ids.push(name) // Compile error: cannot convert value of type 'String' to expected argument type 'Int'

StringStack など、利用する型ごとにクラスが必要?


Genericsを使うと

class Stack<Element> {
    var items = [Element]()
    func push(_ item: Element) {
        items.append(item)
    }
    func pop() -> Element {
        return items.removeLast()
    }
}

var ids = Stack<Int>()
ids.push(id)
ids.push(name) // Compile error: cannot convert value of type 'String' to expected argument type 'Int'

var names = Stack<String>()
names.push(name) 

でも逆に、呼び出し元が決めたい場合も

// これを公開し、実際のstructは隠蔽する
protocol ChatRoom {
    var id: Int { get }
    var name: String { get }
}

// DM: チャットルーム名は参加者名のカンマ区切り、
fileprivate struct DMChat: ChatRoom {
    var id: Int
    var name: String { memberNames.joined(separator: ", ") }
    var memberNames: Array<String>
}

// チーム: 名前やアバターアイコンを持つ
fileprivate struct TeamChat: ChatRoom {
    var id: Int
    var name: String
    var memberIDs: Array<Int>
    var avatarIcon: String
}

func loadDMChat(id: Int) -> ChatRoom {
    return DMChat(id: id, memberNames: ["Taro"])
}

func loadTeamChat(id: Int) -> ChatRoom {
    return TeamChat(id: id, name: "ACCESS", memberIDs: [100,101,102], avatarIcon: "file")
}

loadDMChat() では ChatRoom として内部の型は隠蔽したいんだけど、実際に返るのは常に DMChat

これは擬似コード
func loadDMChat(id: Int) -> <C: ChatRoom> C  {
    return DMChat(id: id, memberNames: ["Taro"])
}

こんな感じに、Reverse Genericsしたい。


Returning an Opaque Type

-func loadDMChat(id: Int) -> ChatRoom {
+func loadDMChat(id: Int) -> some ChatRoom {
    return DMChat(id: id, memberNames: ["Taro"])

こう書くことで、コンパイル時にこの戻り値は DMChat 型とみなされる。

// Compile error: cannot convert value of type 'some ChatRoom' to specified type 'DMChat'
private let dm: DMChat = loadDMChat(id: 1)

仮に DMChat 型が呼び出し元に見えていたとしても、その型で受け取ることを許可しているわけではない。


何が嬉しいのか

  • 型の隠蔽
  • オーバーヘッドがない
    • どんなオーバーヘッド?

Value type

var name: String = "abc"
print(name) // abc

var name2 = name

name.append("1")
print(name) // abc1
print(name2) // abc
  • 実は、SwiftのStringはStructで、値型
  • 値渡し、つまり、メモリの確保、コピーなどが行われれる
    • 実際には、Copy-On-Writeなど最適化されていはいる

https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html
In fact, all of the basic types in Swift—integers, floating-point numbers, Booleans, strings, arrays and dictionaries—are value types, and are implemented as structures behind the scenes.

参照型のシャローコピーにより引き起こされる問題(コピー元も変更されてしまう)の解決として、値の変更の容易さや、イミュータブルクラスを都度まるごと作り直すオーバーヘッドを考え、値型がよいという考え方らしい(?)


終わり

参考URL

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
1