(discord/swift/2018/03/16/0) 関数の返り値の型としてvoidコンテキストの挙動について

概要

  • discord ios dev
    • #swift
    • 2018/03/16
  • 関数の返り値の型としてvoidコンテキストの挙動について

log

rintaro - 2018/03/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について

概要

  • discord ios dev
    • #swift
    • 2018/03/14
  • enumを型定義により実現するパターンについて

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

https://cdn-ak.f.st-hatena.com/images/fotolife/m/moapp/20180504/20180504232543.png?1525443977

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は、

  1. bodyとして、onResolve / onReject / onCancelのObserver型のインスタンスを生成
  2. それらのObserver群はthenをcallしたPromiseのobserversに追加
  3. 元のPromise p がresolved状態になるタイミングでnextPromiseのonResolveが発動
  4. thenの引数として渡したbodyが発火しその結果resolve、もしくはrejectが呼ばれる

という動きになる、そして pnextPromise がいつ発火するのかという話になるのだが、

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)
  • response parser
  • request client

その結果レスポンスを受け取る

  • status code
  • payload
  • response header

これ以外にもstreamだったりkeep-aliveだったり様々な機能が想定される

HTTP Method

github.com

Request Parameter

github.com

様々なAPIクライアントライブラリ

構想するにあたって一般化をしたいため他言語含めたAPIクライアントから学びを得たい

Swift

APIKit

github.com

Alamofire

github.com

Rust

reqwest

github.com

docs.rs

Python

requests

Pythonだとよく使われるのはrequestsなイメージがある

github.com

動的型付け言語であるので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

qiita.com

上記より参考にすると機能的に網羅されてメジャーなライブラリは

  • HTTPClient
  • Faraday

の2つが良さそう

HTTPClient

github.com

Faraday

github.com

【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