cockscomblog?

cockscomb on hatena blog

Rust 1.75.0のasync fn in traits

Rustでツールを書こうとして、コンポーネントを差し替えられるようにtraitとして定義した。GUIプログラミングの習い性で、IOが発生するメソッドは非同期にしたいから、asyncキーワードをつける。ここでは、何か文字列を読み込む予定のLoader traitを定義する。

use std::error::Error;

trait Loader {
    async fn load(&self) -> Result<String, Box<dyn Error>>;
}

このコードを書き始めた時点で、このコードは有効ではなかった。Rustではtraitにasyncメソッドを持たせられなかったのだ。そこで、async-trait crateの出番となる。

use std::error::Error;

use async_trait::async_trait;

#[async_trait]
trait Loader {
    async fn load(&self) -> Result<String, Box<dyn Error>>;
}

これを使ってコードを書くと、こういう感じになった。

(Rust Playground)

Rust 1.75.0のasync fn in traits

2023年12月28日に、Rust 1.75.0がリリースされた。

このバージョンから、traitのメソッドをasyncにできるようになった。impl TraitTraitを実装したなんらかの型を表していて、traitのasync fn-> impl Futureの糖衣構文のような扱いになっている。

これを利用すると、async-trait crateを使わなくても同じように書けるはずなので、書き換えてみる。単に#[async_trait]を除去すると、次のようなエラーにぶつかる。

   |
24 |     loader: Box<dyn Loader>,
   |                 ^^^^^^^^^^ `Loader` cannot be made into an object
   |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>

うわーとなって、「Announcing `async fn` and return-position `impl Trait` in traits | Rust Blog」の記事をよく読み直してみると、制限にぶつかっていることがわかる。特に今回は Dynamic dispatch のところに注目する。

Traits that use -> impl Trait and async fn are not object-safe, which means they lack support for dynamic dispatch. We plan to provide utilities that enable dynamic dispatch in an upcoming version of the trait-variant crate.

動的ディスパッチというのは要するに仮想関数みたいなやつと同じで、コンパイル時に静的に型が決まらないようなケースはまだサポートされていないようだ。ここでさっきのコードを見直すと、確かに、Box<dyn Loader>などとやっている。いずれ公式のtrait-variant crateを使うと、なんらかいい感じにしてくれるようだが、現時点ではまだそういう機能がない。

ということでいったんasync-trait crateに戻ってもよいのだけど、そもそも今回は動的ディスパッチをやめても、いまのところ差し支えない。ChatGPTに「Rustでtraitオブジェクトに対するdynamic dispatchを避けるにはどうしたらよいですか」と聞いてみると、ジェネリクスを使えばいいとわかる(それはそう)。

use std::error::Error;

trait Loader {
    async fn load(&self) -> Result<String, Box<dyn Error>>;
}

struct Processor<L: Loader> {
    loader: L,
}

impl<L: Loader> Processor<L> {
    fn new(loader: L) -> Self {
        return Self { loader };
    }

    async fn process(&self) -> Result<(), Box<dyn Error>> {
        unimplemented!()
    }
}

これで無事にコンパイルできる。

(Rust Playground)

なるほどでした。

2023年の「散財 of the Year」

買ってよかった2023ということで2023年の「散財 of the Year」を発表します。

Best おもちゃ

今年のBestおもちゃはガンプラガンプラは小学生か中学生くらいの頃にいくつか作ったが、それ以来、20年以上ぶりにプラモデルを作った。

小学生になった息子と作ったら楽しかろうと思って、子供でも簡単そうなものと、大人でも楽しめそうなものを少しずつ買った。息子だけでなく4歳の娘もすっかり虜になり、3人でHGのガンプラを作ったりもしている。

自分でも、工具をいくらか買い揃えて、RGを2つとHGを2つ作った。ニッパーでパーツを切り離し、ヤスリでゲート跡を目立たなくして、スミ入れでディテールを足していく。この地味な工程を延々繰り返しながら、最近の出来事を思い返したりする。そういうことをしていると、何やらセラピーのように感じられる。

20年前と比べて今時のガンプラはかなり出来がよく、満足感も高い。細かな作業をしながらセルフリフレクションしたい皆さんにおすすめです。

Best コンピュータ

Bestコンピュータは、MacBook Pro (14-インチ, M3 Pro, Nov 2023)。M3は来年だと思っていたので、驚きつつ購入。スペースブラックがかっこいい。

2023年における計算機の性能を決めるパラメータは、製造プロセスへの依存度が高い。製造プロセスの微細化が進めば、回路規模を大きくしてもダイサイズを維持でき、消費電力も減らせる。ということでTSMCのN3目当てにM1 ProをM3 Proにした。実際、シングルコア性能が向上したためか、GUIがさらに滑らかになったのを感じられる。

Macの性能がいいと、作業へのためらいが減って、少し気楽になるのがよい。

Best ゲームハードウェア

Bestゲームハードウェアは、PlayStation VR 2と少し迷ったが、PlayStation Portalに決めた。

今年は大作ゲームを何本かクリアしていて、ほどほどにコンソールゲームで遊んでいる。PlayStation 5もNintendo Switchもそれぞれちゃんと遊んでいて、もちろんグラフィックスはPlayStation 5に分があるのだが、Nintendo Switchの気やすさの方が好ましく感じられる場面の方が多かった。というところに、Project QことPlayStation Portalが発表。

発売時に手に入れてちょこちょこ遊んでいるが、テレビの前に座らなくていい開放感で、ゲームの体験が大きく変化した。もちろんグラフィックスのよさは(HDRじゃないとか細かいことはあるけれど)折り紙つき。専用ハードウェアでないとできない体験でもあった。

Best 散財

さて、栄えあるBest散財は、3DプリンタBambu Lab P1Sだ。これを買ってから、CADで簡単なモデリングができるようになって、ちょっとしたものを出力している。使用頻度は高くないが、いつでも使える3Dプリンタがあるという事実それ自体に満足している。

モデル共有サイトで自作のモデルを公開してみているが、これまでに500回ダウンロードされ、出力した人がコメントを残してくれている。そういうのもかなり嬉しい。


2023年は円安、物価高で、生活のコストも上がった感があるが、振り返ってみればかなりエンジョイしている。2024年はApple Vision Proあたりでしっかり散財したい。


▶ 【PR】はてなブログ 「AIタイトルアシスト」リリース記念 特別お題キャンペーン
お題と新機能「AIタイトルアシスト」についてはこちら!
by はてなブログ

買ってよかった2023

SwiftにおけるTyped throwsの現在

現在Swift Evolutionで議論されているSE-0413 Typed throwsについて、Swiftの歴史を辿りながら紹介します。

この記事ははてなエンジニア Advent Calendar 2023の9日目の記事です。昨日は id:kouki_daniPadだけでアプリを作ってみるでした。ファスティング中の id:kouki_dan を関モバに誘ったのは私です。お誕生日おめでとうございました。

Swiftのエラーハンドリング

Swiftのエラーハンドリングでは、2015年6月のSwift 2.0のリリース以来、エラーに型がつかない。Errorプロトコルに準拠したなんらかの型が投げられるということだけ決まっていて、それが実際にどうであるかを確認するのは(あるいは確認しないのは)、呼び出し側に任されている。do文のcatch句にはパターンが書けるので、必要に応じてハンドリングできる。

do {
    let xmlDoc = try parse(myXMLData)
} catch let e as XMLParsingError {
    print("Parsing error: \(e.kind) [\(e.line):\(e.column)]")
} catch {
    print("Other error: \(error)")
}

実際にどういった型のエラーが起きるのかは、ドキュメンテーションでしか宣言できない。エラーのハンドリングが網羅的かどうかを機械的に検査することもできない。

Typed throwsに関する初期の議論

このことは度々議論の的となった。2015年12月にはすでに、当時のswift-evolutionメーリングリストで議論されている。Swiftを生み出したクリス・ラトナーは、typed throwsは良いが、Swift 3の resilience モデルまでは問題がある、と返信している。

動的にリンクされるライブラリがエラーをthrowする際に、ライブラリ側が変化してthrowするエラーが変わっても、呼び出し元からはそれを知ることができないから、なんらかの仕組みがないと型の安全性が壊れる、ということだ。

ちなみに当時 resilience モデルと言っていたものは、Swift 3では実現されない。Swift 5.0でのABIの安定化後に、Library Evolutionとして、2019年9月にリリースされたSwift 5.1から利用できるようになった。

エラーの型をパラメータに持つ型

Result

2018年11月にResultを標準ライブラリへ追加するプロポーザルがSwift Evolutionで起案され、1ヶ月後に承認される。そして2019年3月のSwift 5.0でリリースされた。

@frozen public enum Result<Success, Failure> where Failure : Error {
    case success(Success)
    case failure(Failure)
}

これはSwiftのエラーシステムが提供するthrowstrycatchとは全く違う方法でエラーハンドリングを行わせるもので、言語としての一貫性という意味では怪しいところがある。ただし当時の背景からすればこれは妥当で、まだSwift Concurrencyがなく、非同期処理はコールバックで表現されていたため、このようなものが求められていた。実際にサードパーティのResult型が広く使われてもいた。

C++の開発者であるビャーネ・ストラウストラップは「プログラミング言語C++ 第4版」の中で、標準ライブラリの役割のひとつに「ライブラリ間通信を実現するコンポーネントの集合」を挙げている。ライブラリ間でのやり取りに必要な汎用のコンテナ型を提供するのは、標準ライブラリの重要な役割である。したがってResultが標準ライブラリに追加されることには必然性があった。

そしてこのResult<Success, Failure>の型パラメータには、Errorプロトコルに制約されたFailureがある。他のプログラミング言語におけるEither型を考えればこれも妥当であるが、既存のエラーハンドリングモデルとはギャップがある。

Resultgetメソッドやイニシャライザによって、Swiftのエラーハンドリングシステムと相互運用できるようになっている。このときエラーの型はany Errorになる。

@frozen public enum Result<Success, Failure> where Failure : Error {
    @inlinable public func get() throws -> Success
}

extension Result where Failure == any Error {
    public init(catching body: () throws -> Success)
}

Swift ConcurrencyのTask

2021年9月リリースのSwift 5.5で、Swift Concurrencyとしてasync/awaitやActorなどが導入された。ここで導入されたTaskにもResultと同様にFailure型パラメータがある。

@frozen public struct Task<Success, Failure> : Sendable where Success : Sendable, Failure : Error {
}

これもResultに近い。

Primary Associated TypesとAsyncSequence

2022年9月にリリースされたSwift 5.7で、Primary Associated Typeという機能が追加された。標準ライブラリの多くのプロトコルにも設定されたため、この機能でsome Sequence<String>のように書ける。ところが、AsyncSequenceプロトコルにはPrimary Associated Typeが設定されなかった。

AsyncSequence and AsyncIteratorProtocol logically ought to have Element as their primary associated type. However, we have ongoing evolution discussions about adding a precise error type to these. If those discussions bear fruit, then it's possible we may want to also mark the potential new Error associated type as primary. To prevent source compatibility complications, adding primary associated types to these two protocols is deferred to a future proposal. — Primary Associated Types in the Standard Library

Swift Evolutionでは、エラーの型に関する議論が続いているから、とされた。このことでany AsyncSequence<String, any Error>とは書けない。

Typed throws

そして2023年8月に、Status check: Typed throwsが投稿される。9月にはSwift Language Steering GroupのDoug GregorがPitchを投稿し、11月、ついに正式なプロポーザルSE-0413 Typed throwsができた。

実際に試す

ここで実際に動作を試してみる。

最新のdo throws(ErrorType)の構文を試したいので、Swift Forumに投稿された最新のツールチェーンをダウンロードし、~/Library/Developer/Toolchains/に展開する。

Xcodeの場合

Xcodeなら「Xcode > Toolchains」からこれを選択。

あるいは「Manage Toolchains…」でもいい。

Terminalの場合

シェルではツールチェーンのBundle IDを調べてTOOLCHAINS環境変数に設定する。

$ ls ~/Library/Developer/Toolchains/ 
swift-PR-70182-969.xctoolchain

$ /usr/libexec/PlistBuddy -c "Print CFBundleIdentifier:" ~/Library/Developer/Toolchains/swift-PR-70182-969.xctoolchain/Info.plist
org.swift.pr.70182.969

$ export TOOLCHAINS=org.swift.pr.70182.969

$ swift --version                         
Apple Swift version 5.11-dev (LLVM e131e99f323910c, Swift 4d62b1f4e64aa28)
Target: arm64-apple-macosx14.0

実験的フラグの設定

また実験的フラグTypedThrowsを有効にする必要がある。Swift Packageなら、.enableExperimentalFeature("TypedThrows")とするのが簡単だ。

// swift-tools-version: 5.9

import PackageDescription

let package = Package(
    name: "TypedThrows",
    targets: [
        .executableTarget(
            name: "TypedThrows",
            swiftSettings: [
                .enableExperimentalFeature("TypedThrows"),
            ]
        ),
    ]
)

Typed throwsを見ていく

エラーの型を指定するにはthrowsの代わりにthrows(ErrorType)を書く。

enum CatError: Error {
    case sleeps
    case sitsAtATree
}

func callCat() throws(CatError) -> Cat {
    if Int.random(in: 0..<24) < 20 {
        throw .sleeps
    }
    return Cat(name: "Neko")
}

もし宣言したのと違う型のエラーをthrowしようとすれば、そこでコンパイルエラーになる。

func callCatBadly() throws(CatError) -> Cat {
    throw SimpleError(message: "sleeping") // error: Thrown expression type 'SimpleError' cannot be converted to error type 'CatError'
}

catch句では型推論される。

do {
    _ = try callCat()
} catch {
    print(error) // このerrorはCatError
}

ただし、doの中で複数の型のエラーが起きる場合は、any Errorに落ちる。

do throws(ErrorType)で明示的に発生していいエラーを限定できる。もしもそれ以外のエラーが発生するようであれば、そこでコンパイルエラーになる。

do throws(SimpleError) {
    _ = try callCat() // error: Thrown expression type 'CatError' cannot be converted to error type 'SimpleError'
} catch {
    print(error)
}

throws(any Error)throwsと同じ意味で、throws(Never)throwsじゃないのと同じ意味になる。

またエラー型を型パラメータにすることで、rethrowsを置き換えられる。

ということで、全体の規則は難しくない。

Typed throwsを使うべき場面は限定されている

プロポーザルに、Typed throwsを使っていいケースが紹介されている。普通はエラーを網羅的に場合分けしないので、any Errorである方がむしろいい。型があってもいい場面は次の通り。

  • モジュールやパッケージ内に閉じていて、常にエラー処理したい場合は、純粋に実装の詳細であり、もっともらしい
  • ジェネリックなコードで自分自身がエラーを発生させず、利用者が発生させたエラーをそのまま伝える場合
  • 制限された環境下で動作するか、あるいはメモリを割り当てできない場合で、かつ自分自身でしかエラーを作らないとき

1つ目のケースは、つまり外部との境界に表れないなら問題ないということだ。あとからエラーの種類が増えてもモジュール内に閉じているので、特に問題が起きない。

2つ目のケースは、rethrowsと同等の条件だ。これも型は外から与えられるので、実質的にモジュールに閉じる。

3つ目のケースは少し特殊で、組み込み環境のようなものが想定されている。

要するに、モジュールの境界ではまず型をつけない方がいい、ということが書かれている。Typed throwsが利用されすぎることが懸念されている。

Typed throwsの今後

現在のプロポーザルについて、おおよそ全体には好意的に受け止められている。このまま受理されれば、遅くとも来年秋のSwift 5.11頃にリリースされるのではないか。(互換性のためにSwift 6になると少し動作が変化する予定とされている。)

Typed throwsによってResultTaskなどとインピーダンスが揃い、使いやすくなる面が多いだろう。ただしAsyncSequenceにPrimary Associated Typesを設定するのはFuture directionsに示されている通り、for..inの調整も含めて別のプロポーザルを待つ必要がある。

またかねてから議論されていた、throws(FileSystemError | NetworkError)のように複数のエラー型を扱えるようにする話はいったん見送られ、Alternatives consideredに記載された。実質的に匿名enum(直和型と呼ばれることも)を追加することになるため、このプロポーザルのスコープから外されている。


ということで、関西モバイルアプリ研究会A #1で話したTyped throwsでした。

明日は id:papix です。

3DプリンタでMagSafe充電器スタンドをつくる

この秋にリリースされるiOS 17では、充電中のiPhoneに情報を一目でわかるように表示する「スタンバイ」機能が搭載されるそうだ。iPhone 14 Proの常時表示ディスプレイと組み合わせると便利そうだ。

これを活用するには充電中のiPhoneを一定の角度に保つ充電スタンドが必要になる。MagSafe充電器 ワイヤレスなら、充電器ごとに設定を記憶してくれるようだから、MagSafe充電器タイプが望ましい。市場には、MagSafe充電器が一体になったスタンドや、単体のMagSafe充電器と組み合わせて使う製品がある。今回は3Dプリンタを買ったことだから、メイカー精神を発揮してみる。

試作1号

MagSafe充電器の大きさをノギスで測ると、直径は55.9 mm、厚みは5.5 mmある。そこから例によって、Fusion 360モデリングする。MagSafe充電器が少し高い位置に一定の角度で固定されていれば用が足りるので、70度に傾けた枠に愚直に支柱をつける。iPhoneの分だけ重心が手前にくるから、台座も手前に伸ばしてある。

これを3Dプリンタで出力する。ツリー状のサポートをつけている。

一見するとうまくできているが、オーバーハング(せり出し)部分で精度が悪化し、荒れてしまった。内側もその影響を受けて、MagSafe充電器に干渉し、うまく収まらない。パーツを分割して出力するとよいのだろうか。

使用感も確かめてみると、最低限スタンドとしての役には立つ。iPhoneが浮かんで見えるのは格好がよい。ただ、支柱部分の剛性が足りていないのか、iPhoneの重みで僅かなたわみが生まれ、振動してしまう。iPhoneを操作するたびに振動するので、使い心地はよくない。

試作2号

全体的な剛性を確保するために、支柱で支える構造をやめた。ケーブルを裏側にまわすついでに、ひろく穴を空けてMagSafe充電器の充電中の熱が後ろへ逃げるようにしている。

3Dプリンタでの出力時には、MagSafe充電器まわりの精度が出やすいように、倒した状態にしている。

出力してみると、精度は十分で、剛性もある。使用感にも問題がない。

素材が軽いので、iPhoneを近づけると磁力でスタンドの方が動いてしまう。錘をつけると改善されるかもしれないが、困るわけではないので、このままでもよいだろうか。

感想

CADの操作に慣れて、少し複雑な形でもモデリングできるようになった。ただしCADでどんなに自由にかたちを作っても、それでうまくいくとは限らない。素材の特性や加工する装置にあわせて設計しなければ、まさに机上の空論だ。

今回は最低限度、MagSafe充電器スタンドとして使えるものができた。ヒンジで角度を調整できるとか、改良したい箇所もあるが、そもそもヒンジをどう作ったらいいのか、見当がつかない。いくらでも学ぶことがある。

3Dプリンタで何かを作るプロセスは、頭の中にあるものを現実で試し、失敗や成功の経験をするもので、刺激的でおもしろい。これは相当におもしろい。


3Dプリンタを買った

Bambu LabのP1Sという3Dプリンタを買った。少し前に出たばかりの機種で、同じメーカーのP1Pという機種にエンクロージャ(覆い)がついたようなモデルだ。

家庭用の3Dプリンタは安いものなら数万円で買えるが、これは12万円弱で、価格帯としてはミドルレンジにあたるのだろうか。会社の表彰制度で社長賞を頂戴して気が大きくなっていたので、ついに買った。何かを買うときに、少しいいものを選びがちな性分で、安物買いを恐れている。入念にインターネットで検索をして、FDM(熱溶解積層)法で、Core XY方式であり、エンクロージャで覆われている、不具合の少なそうな機種を選んだ。

3Dプリンタを選ぶのは相当に難しいことだ。何を基準に選んだらいいのかも知らないし、進歩が激しい分野だから、状況が変化しやすい。

もしも家電量販店で販売されているような製品であれば、家電量販店の売り場にしばらくいれば、だんだんと事情が掴めてくるものだ。商品のひとつひとつに主だった特徴が書かれていて、例えばパソコンなら、CPUが何で、メモリが何GBで、ストレージがどうで、インターフェースが……、というのがまとめられている。それがドラム式洗濯機であっても、乾燥機能がヒートポンプ式なのかヒーター式なのか、洗濯の容量、洗剤自動投入機能がついているか、一目でわかる。つまりそういったことが製品を差別化していて、それに気を配ればよい、ということだ。

そういうポイントを知らない状態で3Dプリンタを選ぶのは困難だから、かなりの時間をかけて、何となくの知識を得た。

この過程でわかったことだが、3Dプリンタを愛好する人たちの多くにはメイカー文化が根付いていて、3Dプリンタそれ自体もその対象となっている。つまり3Dプリンタそのものについて、自分たちで手を加えやすいものが好まれている。これにはフリーソフトウェア運動的な側面もあって、プリント可能な3Dプリンタのパーツのモデルが多く公開されている。

それはさておいて、何となくの知識でBambu Lab P1Sを注文し、3日後には届いた。

P1Sを設置するのは、付属する印刷物を読めば難しくない。Bambu Labが公開しているYouTubeの動画でも予習してあったので、特に苦も無く済んだ。電源が国内で一般的な2ピンではなく3ピンタイプなので、変換してやる必要はあるが、これも事前にアダプタを用意していたので問題ない。

設置後に早速、Benchyと呼ばれる船のモデルをプリントしてみた。3Dプリンタベンチマークとされる船は、だいたい20分弱できれいに出力された。これは3Dプリンタとしてはかなり早い部類で、Bambu Labのセールスポイントだ。

そこから、Autodesk Fusion 360の個人利用版を使って、CADを学んでいる。スケッチから立体を作るフローがわかってきて、単純な形状なら作れるようになった。今朝は毎月頼んでいるレターのスタンドをプリントしてみた。プリントにかかった時間は30分くらい。まあまあかな。

使い途の当てもなく3Dプリンタを買ったが、自分でモデリングしたかたちをプリントできたとき、えもいわれぬ感慨を得た。コンピュータの中にしか存在しなかったものが、実際に触れるものとしてできあがると、嬉しいものだ。ましてそれが家にいながらにしてとなれば格別である。

Apple Vision Pro所感

Apple Vision Proの発表を見ての感想です。

空間コンピュータ

予想通り、ハードウェア面でもソフトウェア面でも圧倒的な完成度の製品を出してきた、という印象だ。ディスプレイからオーディオ、センサーに至るまで、可能な限りが詰め込まれている。新たなカテゴリをプラットフォームとして切り拓く、硬い意志が感じられる。

製品に「Pro」と名付けつつも、同種の製品カテゴリではかなりコンシューマ向けに寄せられてもいる。映画とか、ゲームとか。この辺りはMacintoshを生み出したAppleらしいところでもある。とはいえ価格帯からは「Pro」カテゴリになるし、将来的により廉価なモデルを発売する余地を確保しているのだろう。

Appleは本日、デジタルコンテンツを現実の世界とシームレスに融合しながら、実世界や周囲の人とのつながりを保つことができる革新的な空間コンピュータ、Apple Vision Proを発表しました。

visionOSは、思っていたよりもずっとコンピューティングにフォーカスされていた印象がある。Apple自身も空間コンピューティングと表現しているが、道具として屋内で普通に使うものとして設計されていて、奇を衒ったところがない。極めてインドア的な、普段からコンピュータを四六時中使っているようなオフィスワーカー向けの製品のように感じられ、つまりこれはMacintoshなんだな、と理解した。

Apple Silicon

Apple Siliconもしっかりと活かされている。性能と効率のバランス、そして既存のアプリとの互換性を考えれば、M2チップが採用されたのは理解しやすい。空間コンピューティングで複数のアプリを同時に実行するには、最低限の性能かもしれない。できればM3を期待したかったところだが、コストや量産する上でのトレードオフがあるのだろう。

M2チップは単体で機能するための比類ないパフォーマンスを提供し、新たに開発されたR1チップは、12のカメラ、5つのセンサー、6つのマイクロフォンからの入力を処理し、コンテンツがユーザーの目の前に現れるような感覚を生み出します。

白眉はR1チップで、空間上にリアリティのあるユーザーインターフェースを描画するための専用チップになっている。R1に多くの処理を肩代わりさせられることで、M2はその性能のほとんどをコンピューティングに使えるのだろう。プレスリリースには次のように記載されていて、これは要するにフレームレートが90fpsということか。

R1は瞬きの8倍高速な12ミリ秒で新しいイメージをディスプレイにデータストリームとして伝送します。

VR的な感覚では、片目あたり4K以上とされる解像度だと、グラフィクスを処理するのに非常に大きな性能が必要に感じられる。しかし実際には、Apple Vision Proの空間コンピューティングでは、単にウインドウを描画する程度の性能でいいはずだ。このウインドウの描画を行うのがM2チップなんだろう。R1チップは描画されたウインドウを空間上にプロジェクションして、カメラの入力と合成する。こちらは片目あたり4Kのグラフィクスを扱うことになる。

ディスプレイ

micro-OLEDで2,300万ピクセルということで、例えば4,150×2,750ピクセルくらいのディスプレイが2枚くらいだろうか。

Appleシリコンのチップにもとづいて開発された画期的な超高解像度ディスプレイシステムを備えたApple Vision Proは、micro-OLEDテクノロジーにより、広色域とハイダイナミックレンジを備えた切手サイズの2つのディスプレイに、合計2,300万ものピクセルを詰め込んでいます。

これが実際にどれくらいきれいに見えるのかは、視野角と関係するから、この情報だけではわからない。例えばPS VR2の水平視野角は110度で、これと同じくらいだとすれば、角画素密度(ピクセル/度)はおおよそ38になり、PS VR2の2倍になる。これくらいの角画素密度があるとよほど小さな文字でなければ普通に読めると思う。

オーディオ

WWDC20で、iOS 14からAirPods Proなどで空間オーディオをサポートすることが発表された。それから3年、空間オーディオが利用できるハードウェアもソフトウェアも増え、そして今回Apple Vision Proでも空間オーディオが大きく取り上げられている。

Apple Vision Proでの体験の中核となるのは、サウンドがユーザーを取り巻く環境から聞こえるような感覚を生み出し、その空間に合わせてサウンドを調整する、先進的な空間オーディオシステムです。

Apple Vision Proの開発期間を考えれば、空間オーディオはそもそもApple Visionプラットフォームの副産物である可能性も考えられる。

空間コンピューティングというものを考えたときに、音がどこから聞こえるかは空間を認識する重要な手掛かりになる。

API

visionOSのアプリは、iOSやiPadOS向けのアプリがほとんどそのまま動くほか、UIKitやSwiftUIで開発できるようだ。UIKitが異なるフォームファクタのOSに使われるのはこれが初めてではなく、例えばtvOSでも利用されている。

SwiftUIはもともとAppleのプラットフォーム間でクロスプラットフォーム指向であり、visionOS向けアプリ開発に推奨されることに驚きはない。開発者にとっても扱いやすい。

総じて、visionOS向けのアプリ開発iOSアプリの開発と同じくらいには容易そうだ。既存のアプリも含めて、ローンチ時から多くのアプリが提供されるだろう。新規のプラットフォームで最初からiOSのエコシステムに相乗りできるのは恩恵が大きそうだ。特に昨今のiPadOSは、プロ向けのアプリを推進してきたところがあって、空間コンピューティングとは相性がよさそうだ。

ゲーミング

Apple Vision Proは、明確にVRのゲームプラットフォームではなさそうだ。ハンドコントローラがなく、ジェスチャのみでVRのゲームを遊ぶのはまだ難しいだろう。そもそもデモに登場したゲームは、PlayStationのコントローラを持って遊んでいる。


いったんこれくらいです。

WWDC23与太話

WWDC23で発表されそうなことを考える遊びを行う。

Swift 5.9

Swift 5.9は大規模なアップデートになる。特にMacroとVariadic Genericsは、ライブラリの提供するAPIに影響しそうだ。

Macro

Macroは今後のSwiftプログラミング体験を大きく変えていく。自分でMacroを定義することもあるかもしれないが、SwiftSyntaxを駆使するMacroは単純なSwiftコードを書くより難解で、再利用性が高くない場面では正当化しにくい。裏を返せば、Macroが役立つ場面は、多くの人の課題を解決する場面ということだ。

便利なMacroを提供するライブラリは増えるだろうし、それはAppleのライブラリも例外ではない。例えばFoundationフレームワークに(Optional Unwrapなしに)文字列からURLを構築するMacroが(要するに疑いようなく便利なMacroが)追加されたりするかもしれない。

Macroはコンパイル時の機能だから、特別何もしなくてもバックデプロイできるのも嬉しい。

Variadic Generics

Variadic Genericsは、型パラメータを可変長にできる機能である。swift-async-algorithmsなどでも見られるように、複数のパラメータを取るジェネリックな関数ではこれまで、パラメータの数が異なるオーバーロードが複数定義されてきた。Variadic Genericsがあれば、そういうことをせずに済む。これもAppleのライブラリ側に実装される可能性がある。

Observation

この他、まだ議論中のObservationにも注目したい。

Cocoaの時代からSwiftUIに至るまで、データの変更を監視するオブザーバーパターンは重要なイディオムだった。KVOやCombineのような仕組みがそれを支えていた。

これをSwiftの言語機能を利用して実現することが議論されている。KVOはObjective-Cランタイムと紐づいた機能であり、CombineはSwift Concurrency以前の仕組みである。そしてこれらは今日のAppleプラットフォームにおけるGUI開発を根本から支えている。したがって、Swift自体にオブザーバーパターンの仕組みが導入されることは、GUIフレームワークがよりSwiftらしくなるのに必要なステップである。

いずれの新機能もSwiftによる開発体験を大きく改善するもので、WWDC23ではこれらが大きく取り上げられるだろうし、Appleフレームワークにもこれらが活かされていると期待される。

Xcode

Xcodeについてはいろいろと思うところがある人が多いと思う。優れたGUIアプリケーションであるとも思うし、年々改善されてはいるものの、不安定さも感じる。同種のソフトウェアと比較すると拡張性に乏しく、レガシーを引きずっている部分もある。

とはいえ毎年丁寧に改善が行われていて、内部的にはここ数年で大きく変化しているようだ。その成果の一つはおそらくSwift Playgroudsで、1年半ほど前のSwift Playgrouds 4.0のリリースから、ついにアプリを開発できるようになった。内部的にはSwift Packageの亜種のような形でプロジェクトが保存されていて、従来のXcode Projectとは異なる。

WWDC23では、この新しい形式のプロジェクト(を発展させたもの)がXcodeでも利用されるようになると期待したい。従来のXcode Projectは複雑なXMLで、慣れていないとコンフリクトの解消も難しい。また同時に、Swift PlaygroundsではなくXcodeそのものがiPadで動作することも期待したい。これは何年も期待されていることだが、先日のiPad向けFinal Cut ProとLogic Proのリリースで、急に現実味を帯びてきている。

加えて、Xcodeプラグインシステムにも改善を望みたいところだ。単純に、拡張できる場所が広がって、例えばGitHub Copilotの拡張機能が普通に導入できると嬉しい。またプラグインをSwift Packageの形で配布したり導入したりできると便利だと思う。

XR

WWDC23の目玉は、当然XR対応のヘッドセットになると思う。もはや誰も疑っていない。

Appleが近年力を入れてきた技術は、ARKitのようなソフトウェア面も、LiDARセンサのようなハードウェア面も、どれもXRヘッドセットに紐づけて考えられる。もちろんMetaのような強力なライバルが先行している分野でもあるが、それに対してAppleの得意とするUIや完成度の面で一定のアドバンテージを出してくるのではないか。

気になるのはもっぱら価格や発売時期で、特に北米以外での発売が遅れたりしないかが気がかりだ。

SwiftUI

SwiftUIは毎年着実に改善されている。WWDC22ではナビゲーションやレイアウト周りが大きく改善され、その他にもひろく手が入っていた。UIKitとの連携も改善された。それまでは不足しているメジャーな機能が足されていく印象だったが、改善のサイクルに入りつつあるのを感じた。

とはいえ、SwiftUIでは実現が困難なこともまだたくさんある。カスタマイズ性が不足しているところは無数にあるが、特にナビゲーションのカスタマイズできなさは気になる。

また、潜在的に宣言的UIフレームワークのSingle Source of Truth的な発想と相性がよくないと思われる、WebViewのようなコンポーネントをどうするかが未決着だ。WebViewは内部に多くの状態を抱えており、多くの状態は外部から自由に操作できない。例えば「読み込み中」かどうかを外部からコントロールできない。こういったいった制御下に置きにくいビューについて、例えばReactのrefのように、何らかの仕組みが欲しい。

逆にそういった不足を補う方向性ではなく、よくあるユースケースをカバーした便利な機能が追加されることも期待したい。例えば、URLのパスベースでナビゲーションを駆動する仕組みは、SwiftUIそのものに含まれていてもおかしくない。またあるいはiPadのためのLogic Proで見られるような、プロ向けのアプリのためのUIが追加されるとおもしろい。

OS

macOS/iOS/iPadOSについて、あまりメジャーな変化が起きるとは思いにくい。例えばmacOSとiPadOSの統合のようなことは、iPadのためのFinal Cut ProやLogic Proが出た以上、想像できない。

UIの改善は一定の規模で行われるようには想像できる。1年前に出たばかりのStage Managerなんかはいかにも改善されそうだ。ウィジェットもさらなる改善があっていい。もっとインタラクティブウィジェットがあると便利だし、macOSやiPadOSで、もっとウィジェットが使いやすくてもいいと思う。

XRヘッドセットとの関係を考えると、ウィジェットはXRでも使えると便利そうだ。あるいは通知についても、XRヘッドセットに合わせて改良される可能性がある。

App Intentsは進展があることを期待したい。アプリの機能をシステムに公開する仕組みであるApp Intentsは、ショートカットアプリやSiriとの連携に利用できる。これがもっと拡張されて連携可能な箇所が増えるとおもしろい。アプリ間でも連携したい。

Swift Chartsも描画できるチャートの種類が増えると嬉しい。例えばPieチャートとか。

あとは、iPhone 14 Proの常時表示ディスプレイの用途が広がると良いと思う。Appleの地図アプリでは常時点灯ディスプレイにナビゲーションを表示し続けているが、これはサードパーティには開放されていない機能だ。

Web技術

Web技術に関連して、iOS 16ではWeb Pushがついに実装されたりした。Appleは別にWeb技術に関心がないわけではなく、ユーザーの利益を重視してやっていると思う。その流れでは、やはりSafariの更新をOSの更新と分離して、もっと高頻度かつ古いOSに向けてもアップデートされてほしい。

セキュリティ面では、Passkeysに関して、ブラウザ拡張がPasskeysを扱えるようになったりするんじゃないか。

サイドローディング

またご時世的に、アプリのサイドローディングが話題である。Appleも法律には抗えないから、これはいずれできるようになりそう。macOSと同じようにNotalizationくらいはするだろう。WWDC23で発表されるかはわからない。サイドローディングまでいかずとも、ChromiumGeckoのようなWebKit以外のブラウザエンジンくらいは解放されるような気はする。

ハードウェア

M2ベースのプロセッサを搭載したMacが発表されることはありそう。TSMCのN3世代の製品(M3?)にはまだ早いんじゃないか。

発表されそうにないこと

大規模言語モデルを活用したSiriみたいなものはまだ出てこない気がする。Apple機械学習そのものや自然言語処理については着実に利用を広げているが、全体的にオンデバイスを好む傾向にある。例えばHomePodのSiriに今日の予定を聞くと、近くのiPhoneのカレンダー情報を取得して返答する。そういうポリシーだから、大規模言語モデルの活用は遅くなりそうだ。とはいえ機械学習を活用した機能自体はもう少し増えていくような気はする。

以上です

いろいろ考えていくと、何にしても幅広くいろいろ発表されそうだし、楽しみですね。