(discord/swift/2018/03/16/0) 関数の返り値の型としてvoidコンテキストの挙動について
概要
- discord ios dev
- #swift
- 2018/03/16
- 関数の返り値の型としてvoidコンテキストの挙動について
log
rintaro
- 2018/03/16
Rubyで「そのメソッドがvoid contextで呼ばれた否か」がとれるといいんだが…(Perlだととれる)。
— FUJI Goro (@__gfx__) 2018年3月16日
返り値返さない版を用意したらええのでは・・・(そんなことはわかってるだろうからなんか理由があるんやろうな
— おも🐈ちメ🐍タル (@omochimetaru) 2018年3月16日
を見て
func foo() -> Int { print("Int"); return 1 } func foo() { print("Void") } foo() // error: ambiguous use of 'foo()'
omochimetaru
- 2018/03/16
返り値オーバーロードだ
なるほど・・・w
rintaro
- 2018/03/16
Void のコンテキストなら下ので確定できたら便利なのかな?
t.ae
- 2018/03/16
func foo() -> Int { print("Int"); return 1 } func foo() { print("Void") } foo() // error: ambiguous use of 'foo()' _ = foo() // error: ambiguous use of 'foo()' () = foo() // Void
最後の記法がちょっとおもしろい
rintaro
- 2018/03/16
ほほー
omochimetaru
- 2018/03/16
_ = foo() なら Void 優先してほしいね。 いや?それもへんか。 捨ててるんだからなんかあるのか。
t.ae
- 2018/03/16
それは返り値握りつぶしてる感あるのでむしろIntが呼ばれてそうにみえますね
omochimetaru
- 2018/03/16
そうですねえ・・・
koher
- 2018/03/16
() = ... とかできるのか。 foo() は void と解釈してほしいかも。
@discardableResult との兼ね合いをよく考えないといけなさそうだ。
@discardableResult func foo() -> Int { print("Int"); return 1 } func foo() { print("Void") } foo() // どっちになってほしい?
t.ae
- 2018/03/16
fooだから分かりにくいですけどガイドラインに従ってればappendとappendingとかみたいになるんであんまり問題起こらない気がしますね。
mutating funcの場合に限るかそれは。
koher
- 2018/03/16
今はそもそもがオーバーロードしたときに見分けてくれた方がいいかという前提の話なのでは?
t.ae
- 2018/03/16
うんまぁそうですが
koher
- 2018/03/16
実際には問題になるケースが少ないから
ambiguous でいいんじゃないかってことか。
t.ae
- 2018/03/16
処理内容が同様ならdiscardableResultだけでいいし、処理内容が異なるならオーバーロードするのが間違っている気がします
Kishikawa Katsumi
- 2018/03/16
コンパイルエラーで全然いいんだけどエラーメッセージをわかりやすくしてほしいと思っています。
rintaro
- 2018/03/16
たぶん元コンテキストのモチベーションとしては、
@discardableResult func foo() -> Int { print("Int"); return 1 }
が既にありそれを使っているプロジェクトなんだけど、返値の生成にそれなりの負荷があるので、
func foo() でオーバーロード出来れば、軽量化出来る可能性があると。
omochimetaru
- 2018/03/16
あと元コンテキストはRubyですね。
Kishikawa Katsumi
- 2018/03/16
戻り値の型によるオーバーロードを使ったAPIを提供しているSDKをメンテしていた経験でいうと。
t.ae
- 2018/03/16
返値の生成にそれなりの負荷がある
そういうケースはありますね……
rintaro
- 2018/03/16
はい、Ruby はとりあえず無視でw
omochimetaru
- 2018/03/16
Swiftだったら別に普通に呼び出してる箇所全部リファクタリングできるから別名でオーバーロードで解決しそう・・・
Kishikawa Katsumi
- 2018/03/16
書き換えが許されるならオーバーロードを作って呼び出し元で戻り値の型を区別するのでもSwiftなら一緒じゃないですか?
別名をつけなくても。
どっちがわかりやすいとかは別として。
omochimetaru
- 2018/03/16
あー、それはそうですね。
tarunon
- 2018/03/16
discardableResult外してVoid overloadでいいじゃん、みたいな気がした
あ、だめなのか
(discord/swift/2018/03/15/0) tryとthrowをwrapしたrecover拡張について
概要
- discord ios dev
- #swift
- 2018/03/15
- tryとthrowをwrapしたrecover拡張について
log
omochimetaru
- 2018/03/15
@lovee こんなのできた
public func recover<R>(_ f: () throws -> R, ifError: (Error) -> R) -> R { do { return try f() } catch let error { return ifError(error) } } public func recover<R>(_ f: () throws -> R, ifError: (Error) throws -> R) throws -> R { do { return try f() } catch let error { return try ifError(error) } }
この前言っていたguard + tryみたいなパターンは、R をOptional にして使えば
guard let x = recover (...) else { return }
の形で書けるし、
2つめのオーバーロードを使えば、リカバリできないエラー型の場合は
そのまま更に上まで飛んで行く。
使用例
guard let modelScene = recover({ () -> SCNScene? in try SCNScene(url: url) }, ifError: { error in self.handleError(error) return nil }) else { return }
lovee
- 2018/03/15
ふむふむ、だいぶ長くなってしまうところ以外は良さげですね
omochimetaru
- 2018/03/15
うん、あとOptional推論がうまくいかないから明示しているんだよね
名前ちょっと変えてかならず Optional になる仕様のほうが使いやすいかも
lovee
- 2018/03/15
もしかすると ifError のところと最後のところの戻り値は Optional
omochimetaru
- 2018/03/15
あ〜 recover 自体を try? でたたむ手があるな
guard let modelScene = try? recover({ try SCNScene(url: url) }, ifError: { error in self.handleError(error) throw error }) else { return }
lovee
- 2018/03/15
できた
import SceneKit public func recover <R> (_ f: () throws -> R, ifError: (Error) -> Optional<R>) -> Optional<R> { do { return try f() } catch let error { return ifError(error) } } func test() { let url = URL(fileURLWithPath: "http://www.google.com") guard let _ = recover({ () -> SCNScene in try SCNScene(url: url) }, ifError: { error in print(error) return nil }) else { return } }
omochimetaru
- 2018/03/15
これだと型ヒントは消せる
lovee
- 2018/03/15
ファイル名対応してないんだDiscord
omochimetaru
- 2018/03/15
そのパターンでもクロージャの型ヒント消せそう。
guard let modelScene = recoverToOptional({ try SCNScene(url: url) }, ifError: { error in self.handleError(error) return nil }) else { return }
行数短くしたいですねえ。
guard let modelScene = recoverToNone({ try SCNScene(url: url) }, ifError: self.handleError) else { return }
lovee
- 2018/03/15
よし、ここまで書けた
import Foundation public func recover <R> (_ f: () throws -> R, ifError: (Error) -> Void) -> Optional<R> { do { return try f() } catch let error { ifError(error) return nil } } struct Test { let url: URL enum InitError: Error { case unknown } init(urlString: String) throws { guard let url = URL(string: urlString) else { throw InitError.unknown } self.url = url } } func test(_ string: String) { guard let test: Test = recover({ return try Test(urlString: string) }, ifError: { print($0) }) else { return } print(type(of: test)) } test("http://www.google.com") // Test test("") // unknown
omochimetaru
- 2018/03/15
try test のところのreturnいらなそう
僕はそのrecoverはrecoverToNoneって名前にした
lovee
- 2018/03/15
まあ1行だけならね
tarunon
- 2018/03/15
nits 第一引数に @autoclosure つけるといいかも
koher
- 2018/03/15
↑を読みながら @autoclosure が良さそうだなと思って最後まで読んだら先に言われてた:rolling_eyes:
omochimetaru
- 2018/03/15
autoclosureもありだけど個人的には何が起きてるかわかりにくくなりそうなので微妙だなあ
koher
- 2018/03/15
用途を考えると single-expression closure になることが多いだろうからまさに @autoclosure の出番だと思うけどなぁ。
omochimetaru
- 2018/03/15
まあ特に標準提案したいというほどの気持ちは無いのでお好みで
tarunon
- 2018/03/15
多分なんですがthrowsの値を使って何かするみたいなfunction書くときはそもそもそれをthrowsにして、
非throwsとの境界をそこに設定するのが良いのではと思った
func doSomething() { return try? doSomething() ?? /* 何か */ } func doSomething() throws { let x = try throwableFunc1() let y = try throwableFunc2() ... }
こんなイメージ
omochimetaru
- 2018/03/15
↑それだとtry?でnilになるから「throwsの値」(飛んでくるエラーオブジェクトの事だよね)が使えない
tarunon
- 2018/03/15
あーー ハマり たしかにそうだ
omochimetaru
- 2018/03/15
エラーを使いながらもthrow伝搬を止めて値に畳み込みたいときに、簡単に書くことができないんだよね。
do - catch だと式じゃないから式の中に埋め込めないし。
(discord/swift/2018/03/14/0) Enum{n}Convertibleについて
概要
log
omochimetaru
- 2018/03/14
@tarunon 完全にこれが欲しくなってきた
https://forums.swift.org/t/automatically-derive-properties-for-enum-cases/10843
↑これの ifCase0 に相当するものを自動でつくる
Gist ObservableEnumConvertibleExtensions.swift
tarunon
- 2018/03/14
わかる~~
omochimetaru
- 2018/03/14
この提案みたときは、そんなん必要なときにちょちょいと定義すればええやんけ、って思ったけど・・・
tarunon
- 2018/03/14
苦痛やろ
omochimetaru
- 2018/03/14
enumを、「A, B, C, Dのどれか」として使おうとしていて、
Observableでもなんでもいいけど、モナドMにそのenumが入ってるときに
M<A?> に簡単にmapしたいという需要はあるね
enumそのまま制御してるときはswitch-caseでいいけど、
ヨリ分けてその先別個に何かやるみたいなパターンがあるんだな。
tarunon
- 2018/03/14
なんかまだはっきりとわからないんだけど
より分けてその先で使いたいパターンっていうのが
本当にEnumである必要があるのか、について吟味したほうがいいかもしれない。
omochimetaru
- 2018/03/14
そこはそうだと思うけどねえ
【WIP】apple/swift - stdlib/public/runtime コードリーディング(全体)
- 概要
- ErrorObject.h
- CFRuntimeBase
- SwiftErrorHeader
- SwiftError
- swift_allocError
- swift_deallocError
- ErrorValueResult
- swift_getErrorValue
- swift_errorRetain
- swift_errorRelease
- swift_willThrow
- swift_errorInMain
- swift_unexpectedError
- _swift_stdlib_bridgeErrorToNSError
- tryDynamicCastNSErrorToValue
- getNSErrorClass
- getNSErrorMetadata
- _swift_lldb_offsetof_SwiftError_typeMetadata
- _swift_lldb_sizeof_SwiftError
概要
ErrorObject.h
CFRuntimeBase
struct CFRuntimeBase { void *opaque1; void *opaque2; };
CoreFoundation/CFRuntime.hからコピーしてくるための定義とあり
swift-corelibs-foundation/CFRuntime.h at master · apple/swift-corelibs-foundation · GitHub
そこの定義はこうなっている
typedef struct __CFRuntimeBase { // This matches the isa and retain count storage in Swift uintptr_t _cfisa; uint32_t _swift_strong_rc; uint32_t _swift_weak_rc; // This is for CF's use, and must match _NSCFType layout _Atomic(uint64_t) _cfinfoa; } CFRuntimeBase;
SwiftErrorHeader
struct SwiftErrorHeader { // CFError has a CF refcounting header. NSError reserves a word after the // 'isa' in order to be layout-compatible. CFRuntimeBase base; // The NSError part of the object is lazily initialized, so we need atomic // semantics. std::atomic<CFIndex> code; std::atomic<CFStringRef> domain; std::atomic<CFDictionaryRef> userInfo; };
SwiftError
Swift.Errorのboxing用のレイアウト定義
type
ボックスに含まれるSwiftエラー値型
このメンバはnative Swift errors
でのみ使用できる
const Metadata *type;
errorConformance
Error
準拠のwitnessテーブル
このメンバはnative Swift errors
でのみ使用できる
const WitnessTable *errorConformance;
hashableBaseType
Hashable
準拠を導入する基本型
このメンバはnative Swift errors
でのみ使用でき、遅延初期化されている
直接使用する代わりにgetHashableBaseType()
を呼び出す
mutable std::atomic<const Metadata *> hashableBaseType;
hashableConformance
Hashable
準拠のwitnessテーブル
このメンバはnative Swift errors
でのみ使用でき、遅延初期化されている
直接使用する代わりにgetHashableConformance()
を呼び出す
mutable std::atomic<const hashable_support::HashableWitnessTable *> hashableConformance;
getIndirectValue
二次的な参照がされるボックス参照内に含まれる値へのポインタを取得する
static const OpaqueValue *getIndirectValue(const SwiftError * const *ptr) { if ((*ptr)->isPureNSError()) return reinterpret_cast<const OpaqueValue *>(ptr); return (*ptr)->getValue(); } static OpaqueValue *getIndirectValue(SwiftError * const *ptr) { return const_cast<OpaqueValue *>(getIndirectValue( const_cast<const SwiftError * const *>(ptr))); }
getValue
値へのポインタを取得する
値は固定ヘッダの末尾に割り当てられる
const OpaqueValue *getValue() const { assert(!isPureNSError()); auto baseAddr = reinterpret_cast<uintptr_t>(this + 1); unsigned alignMask = type->getValueWitnesses()->getAlignmentMask(); baseAddr = (baseAddr + alignMask) & ~(uintptr_t)alignMask; return reinterpret_cast<const OpaqueValue *>(baseAddr); } OpaqueValue *getValue() { return const_cast<OpaqueValue*>( const_cast<const SwiftError *>(this)->getValue()); }
swift_allocError
TBD
swift_deallocError
TBD
ErrorValueResult
TBD
swift_getErrorValue
TBD
swift_errorRetain
TBD
swift_errorRelease
TBD
swift_willThrow
TBD
swift_errorInMain
TBD
swift_unexpectedError
TBD
_swift_stdlib_bridgeErrorToNSError
TBD
tryDynamicCastNSErrorToValue
TBD
getNSErrorClass
TBD
getNSErrorMetadata
TBD
_swift_lldb_offsetof_SwiftError_typeMetadata
TBD
_swift_lldb_sizeof_SwiftError
TBD
malcommac/Hydra コードレビュー2 (then)
概要
- 前回HydraのPromiseについてコードを読んでいった
- 今回はPromiseを使う上で無視できないthenを深掘りしていく
then
Hydra/Promise+Then.swift at master · malcommac/Hydra · GitHub
定義を見ていく
@discardableResult public func then<N>( in context: Context? = nil, _ body: @escaping ( (Value) throws -> N) ) -> Promise<N> @discardableResult public func then<N>( in context: Context? = nil, _ body: @escaping ( (Value) throws -> (Promise<N>) )) -> Promise<N> @discardableResult public func then( in context: Context? = nil, _ body: @escaping ( (Value) throws -> () ) ) -> Promise<Value>
上の定義を見るとthenの呼び出し方は3種類あり、以下のように捉えられる
- 現在のPromiseを発火させた結果、解決したValueを元に
- 指定した型の値(
N
)として後続のPromiseに繋げるValue -> N
- 指定した型のPromise(
Promise<N>
)として後続に繋げるValue -> Promise<N>
- 現在のPromiseの型(
Promise<Value>
)として後続に繋げるValue->Void
- 指定した型の値(
そして@discardableResult
のattributeが付いているため代入操作をしなくても
// some: Promise<Some> としたとき some.then { _ in }
この書き方でPromiseを発火させることができる
実装を見ていく
let p = Promise<Bool> { resolve, _, _ in resolve(true) }
Promise<Bool>
型のインスタンスとして、ただtrueをresolveするPromise p
を生成する
そしてpに対して
then (in context: Context? = nil, _ body: @escaping ( (Value) throws -> () ) ) -> Promise<Value>
上記thenを実行する
p.then { (ret) in print("[then end]: \(ret)") }
そうすると関数内部でnextPromiseという変数が生成される
let nextPromise = Promise<Value>(in: ctx, token: self.invalidationToken, { resolve, reject, operation in let onResolve = Observer.onResolve(ctx, { value in do { try body(value) resolve(value) } catch let error { reject(error) } }) let onReject = Observer.onReject(ctx, reject) let onCancel = Observer.onCancel(ctx, operation.cancel) self.add(observers: onResolve, onReject, onCancel) })
このnextPromiseは、
- bodyとして、onResolve / onReject / onCancelのObserver型のインスタンスを生成
- それらのObserver群はthenをcallしたPromiseのobserversに追加
- 元のPromise
p
がresolved状態になるタイミングでnextPromiseのonResolveが発動 - thenの引数として渡したbodyが発火しその結果resolve、もしくはrejectが呼ばれる
という動きになる、そして p
と nextPromise
がいつ発火するのかという話になるのだが、
thenが呼ばれたタイミングでこの2つのPromiseのrunBodyを実行する
nextPromise.runBody() self.runBody() return nextPromise
こうすることで元の p
のresolve(true)が呼ばれresolved状態からのnextPromiseの発火により
チェインしてるように見せることができる
あとは非同期で連結で実行するための定義tipsとして単純な実行、map、flatMapのように扱うために3種類のパターンで
オーバーロードで定義されているのは使い勝手としても良さそうである
public func then( in context: Context? = nil, _ body: @escaping ( (Value) throws -> () ) ) -> Void
のように用意せず @discardableResult
を使うことでシンプルなインターフェースになってると言えそう
終わりに
今回はPromiseのthenのみについて深掘りをした、自分で非同期ライブラリを実装するような場合に参考になるレビューだったように思う
次回はcatchを深掘りしていく予定
構想 - APIクライアントライブラリ
概要
APIクライアントライブラリをこう作りたいっていう構想をまとめたい
APIクライアントで必要な要素として以下のようなコンポーネントが想定される
まずはリクエスト
- http method
- parameter
- request header
- encoder(encoding)
- json
- url
- response parser
- request client
その結果レスポンスを受け取る
- status code
- payload
- response header
これ以外にもstreamだったりkeep-aliveだったり様々な機能が想定される
HTTP Method
Request Parameter
様々なAPIクライアントライブラリ
構想するにあたって一般化をしたいため他言語含めたAPIクライアントから学びを得たい
Swift
APIKit
Alamofire
Rust
reqwest
Python
requests
Pythonだとよく使われるのはrequestsなイメージがある
動的型付け言語であるのでSwiftだと表現できないような場合もありそうだけど見ていく
リポジトリより機能の一覧を見ると
- International Domains and URLs
- Keep-Alive & Connection Pooling
- Sessions with Cookie Persistence
- Browser-style SSL Verification
- Basic/Digest Authentication
- Elegant Key/Value Cookies
- Automatic Decompression
- Automatic Content Decoding
- Unicode Response Bodies
- Multipart File Uploads
- HTTP(S) Proxy Support
- Connection Timeouts
- Streaming Downloads
- .netrc Support
- Chunked Requests
上記のようにまとめられている
Ruby
上記より参考にすると機能的に網羅されてメジャーなライブラリは
- HTTPClient
- Faraday
の2つが良さそう
HTTPClient
Faraday
【WIP】apple/swift - stdlib/public/core/Codable.swift.gyb コードリーディング
- 概要
- エンコード
- デコード
概要
https://github.com/apple/swift/blob/master/stdlib/public/core/Codable.swift.gyb
gyb : codable_types
gybによる定義で書かれていて、primitiveな型のみに絞って自動生成される? TBD
codable_types = ['Bool', 'String', 'Double', 'Float', 'Int', 'Int8', 'Int16', 'Int32', 'Int64', 'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64']
CodingKey
エンコーディングとデコーディングのキーとして使用できる型とある
文字列と数値によって定義が分かれていて、
文字列
var stringValue: String { get } init?(stringValue: String)
数値
var intValue: Int? { get } init?(intValue: Int)
このようになっていて文字列がkeyは分かりやすいが、数値がkeyのパターンでの用途は珍しいように感じる
CodingKeyを使う場面としてはCodableプロトコルに準拠しつつエンコードorデコード時にkey名が違うような場面で必要になる
まだこの定義だけだとその辺りの組み替えは見受けられないので利用側の定義を見ていきたい
Codable
外部表現の出入りを変えることができる型定義
public typealias Codable = Encodable & Decodable
CodingUserInfoKey
エンコード・デコード時に用いられるユーザー定義key
RawRepresentable
プロトコルに準拠しており、associated typeとしてRawValueにStringを指定しそのままkeyとなる
_DictionaryCodingKey
@usableFromInline // FIXME(sil-serialize-all) @_fixed_layout // FIXME(sil-serialize-all) internal struct _DictionaryCodingKey : CodingKey
Dictionaryのextensionとしてconditional conformanceな以下の定義で利用されている内部コンテナのinternalなkey定義
extension Dictionary : Encodable where Key : Encodable, Value : Encodable extension Dictionary : Decodable where Key : Decodable, Value : Decodable
CodingKeyプロトコルを親としている
このようにDictionaryをEncodable&Decodableに準拠させることでCodableな要素のみを含んだ場合にCodableの枠組みとして扱えるになっている
_GenericIndexKey
@usableFromInline // FIXME(sil-serialize-all) @_fixed_layout // FIXME(sil-serialize-all) internal struct _GenericIndexKey : CodingKey
DecodingErrorで使われているcontainerのindexからkeyとして扱うのに用いられているinternalなkey定義
intValueとstringValueからkeyの値を取る方式のCodingKeyプロトコルのintValueのみをkeyとして扱うように制限を加えられている模様
エンコード
Encodable
外部の表現に自身をエンコードすることができる型
encode
Encodableに定義されている唯一の関数
func encode(to encoder: Encoder) throws
この値を指定されたEncoderにエンコードする
TBD
Encoder
TBD
codingPath
var codingPath: [CodingKey] { get }
エンコード時にこのポイントに到達するために使用されるCodingKeyのパス
userInfo
var userInfo: [CodingUserInfoKey : Any] { get }
エンコーディングのためにユーザーによって設定されたコンテキスト情報
container
func container<Key>(keyedBy type: Key.Type) throws -> KeyedEncodingContainer<Key>
unkeyedContainer
func unkeyedContainer() throws -> UnkeyedEncodingContainer
TBD
singleValueContainer
func singleValueContainer() throws -> SingleValueDecodingContainer
TBD
KeyedEncodingContainerProtocol
TBD
KeyedEncodingContainer<K : CodingKey>
TBD
UnkeyedEncodingContainer
TBD
SingleValueEncodingContainer
TBD
EncodingError
TBD
_KeyedEncodingContainerBase<Key : CodingKey>
TBD
_KeyedEncodingContainerBox<Concrete : KeyedEncodingContainerProtocol>
TBD
デコード
Decodable
外部の表現から自身をデコードすることができる型
decode
Decodableに定義されている唯一の関数
init(from decoder: Decoder) throws
TBD
Decoder
TBD
KeyedDecodingContainerProtocol
TBD
KeyedDecodingContainer<K : CodingKey>
TBD
UnkeyedDecodingContainer
TBD
SingleValueDecodingContainer
TBD
DecodingError
TBD
_KeyedDecodingContainerBase<Key : CodingKey>
TBD
_KeyedDecodingContainerBox<Concrete : KeyedDecodingContainerProtocol>
TBD