Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: "APIデザイン ガイドライン"を追加 #870

Merged
merged 2 commits into from
Nov 11, 2024

Conversation

qryxip
Copy link
Member

@qryxip qryxip commented Nov 9, 2024

内容

#632 に続く形で、独立した"APIデザイン ガイドライン"を追加する。

内容としてはとりあえずRust 以外の言語の APIとして、newtype以外も言及したものを用意する。

関連 Issue

Refs: #631, #854 (comment)

その他

@qryxip qryxip requested a review from Hiroshiba November 9, 2024 15:00
Comment on lines 14 to 15
* 「範囲」を表すデータ型が言語レベルである場合は、可能な限りそのデータ型を用いてAPIを構成するべきです。
* 例えばSwiftやRubyでは`Range`という名のデータ型を使って表現します。
Copy link
Member

@Hiroshiba Hiroshiba Nov 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これ、その言語のRangeがサポートしているものと、APIがサポートするrangeが異なる場合があってややこしいかもです!

例えばたしかPythonはrange(0, 3, -1)とかにすると、逆向きの範囲を指定できます。たぶん[2, 1, 0]
あと飛び飛びの範囲も指定できます。range(0, 4, 2)だとたぶん[0, 2]
このとき、例えばrender()で期待されるレンダリング結果は何になるべきなのか考えれなそうです。

一言でいうと、rangeは範囲だけを表してるものではないので、範囲だけを表したい場合にrangeを使うのは良くないのかもと・・・(今気づきました)

Copy link
Member Author

@qryxip qryxip Nov 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これの対象はSynthesizer::renderの引数ですね。なのでPythonだとおそらく、仮にstepに対応したとしてもrange ― というより多分sliceオブジェクトですが ― を引数に取るのはやめた方がよいという感じになると思います。

# こっちより多分、
synthesizer.render(audio_feature, slice(0, length, 10))
# こう
synthesizer.render(audio_feature, 0, length, 10)

多分正しいのはRust APIのrenderの引数をRangeにした上で、そのrenderを名指しして他のAPIもそうしろという感じになると思うのですが、今はまだ #867 でもそうなっていないわけでどうしたものか…という感じでいます。

Copy link
Member

@Hiroshiba Hiroshiba Nov 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あ〜〜〜なるほどです、RustのRangeは本当に範囲で、かつend >= startなんですね!
たしかにすくなくともRustは使ってもよいのかも。

いやでもrender()はend=startのときにエラーを返す設計になるかもな気がします。
個人的には一旦何も書かず、render側でRangeを使おうってなったら書き足すのが良い気がしてます。

(Range使いたい寄りだと把握しました!)

Copy link
Member Author

@qryxip qryxip Nov 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど。そうしましょう!

0832bdd (#870)
(消したと同時にtodoを残しました)


render()はend=startのときにエラーを返す設計になるかもな気がします。

start > endはともかくstart == endだと空と解釈されると思います。他言語的にもそれが自然かと。
(実装レベルだと空テンソルをONNX Runtimeに渡したらどうなるのか私は知りませんが、別途チェックすればよさそう)

start > endだと、この(↓)表とかを考えると慣習に反しうりそうなので補足は要りますね。
#867 (comment)

ちなみにRustでリテラルを使って20..10とか書くと、denyレベルで怒られます。
image
[追記] エラーのスクショがちょっと適切じゃなかったので差し替え

Copy link
Member

@Hiroshiba Hiroshiba Nov 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

startは含むけどendは含まないの、普通の感覚で行くとものすごい分かりにくいということに気づきました 😇
Rangeだとかがそうなってるのは、「そう設計しておくと色々便利だから」以外に説明が思いつかず、APIで同じことをすると不慣れな人にとってはなぜそうなっているかわからない気がしました。。。

(例えばstart: 10・end: 10だったとき、10を含まないのが直感的じゃない)

開区間、あるいは閉区間になってることが分かりやすい用語を探しました。
採用するかは置いといて、minmaxが一番わかりやすそうではありました。(けど予約語なことも多そうなので迷いどころ)
fromtoも良さそうだけど、小さい値がどちらかは分かりづらそう。
offsetcountlength)も悪くなさそう。
https://chatgpt.com/share/673102c7-9858-8008-a5d1-fb36a5ac0382

これめちゃくちゃ厄介ですね。。。
RustはRangeで良いかもだけど、例えばC APIでstart・endにしてendは開区間にすると、まあ間違いなく閉区間だと思ってバグって使う人が出てくる気がする。。
まあrender()したものを聞いたときに、音声が不連続になるので違和感には気づけそうですが・・・・・・・。

追記:おそらく(offset, length)が使いやすい&わかりやすい気がする。
RustはRangeでも良いかも。このときRange(offset, offset+length)と書けるので相性も良い。
start=endのときの挙動は、サポートしてもしなくてもどちらでも良さそう。どちらにせよドキュメントに明記したい。

Copy link
Member Author

@qryxip qryxip Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これ(↓)は以前私がした提案ですが、C APIとJava APIだけKotlinにならってbegin/start, end_exclusiveとし、他の言語では言語に合った名称 ― 例えばPythonではstart, stop ― にしておけば十分なのではと思っています。
#854 (comment)

というのも

  1. プログラミングの経験がある人で $[i, j)$$[i, j]$ かを気にせずにコードを書く人は多分ほぼいない
  2. "start"や"end"といった語彙が言語の標準にある場合、その語彙はその言語において一意だとしてよいはず
    • 例えばPythonで"start"と"stop"という言葉が出たら、それは $[\texttt{start}, \texttt{stop})$ という区間を意味する。NumPyもそう
    • [追記] Rustでは"end"といってもinclusiveかexclusiveかは一意ではない(後述するが実は"start"も一意ではない)けど、Range<_>RangeInclusive<_>といったくくりのオブジェクトで取り回す文化であるため、問題ではない
  3. [追記] もし言語レベルでの明確な単語が無く、また範囲データ型のようなものが無い場合でも、Kotlinにならってend_exclusiveとでもすればよい
  4. 1., 2., 3.を踏まえると、プログラミング初学者に対しての役割はドキュメントが持つべき

だと思った次第です。

参考:

VOICEVOX/voicevox_core#854 (comment)
Ruby, C++「begin...end
Python 「start:stop
Rust, Scala, C# 「start..end
Kotlin 「start..<endExclusive
Swift 「first..<last
Erlang 「lists::seq(From, To)

https://discord.com/channels/879570910208733277/893889888208977960/1296122640406417468


(offset, length)

ユーザーのニーズには多分合っていると思うのですが、今現在はみ出すような範囲指定を不正入力扱いすると決めているため、"end"側の処理については絶対位置を意識せざるを得なくなり不便なのでは、という気もします。


ちなみに豆知識その2

Rustでは $(i, j]$$(i, j)$ も一応は表現できます。他の言語にはあまり無さそうですが…

use std::ops::Bound;

let a = [1, 2, 3, 4, 5];

// {`Included(i)`, `Excluded(i)`, `Unbounded`}²で、3²=9通り

// [i, j)
assert_eq!([2, 3], a[(Bound::Included(1), Bound::Excluded(3))]);
assert_eq!([2, 3], a[1..3]); // ショートハンド

// [i, j]
assert_eq!([2, 3, 4], a[(Bound::Included(1), Bound::Included(3))]);
assert_eq!([2, 3, 4], a[1..=3]); // ショートハンド

// [i, 右端]
assert_eq!([2, 3, 4, 5], a[(Bound::Included(1), Bound::Unbounded)]);
assert_eq!([2, 3, 4, 5], a[1..]); // ショートハンド

// (i, j)
assert_eq!([3], a[(Bound::Excluded(1), Bound::Excluded(3))]);
// ショートハンドは無い

// (i, j]
assert_eq!([3, 4], a[(Bound::Excluded(1), Bound::Included(3))]);
// ショートハンドは無い

// (i, 右端]
assert_eq!([3, 4, 5], a[(Bound::Excluded(1), Bound::Unbounded)]);
// ショートハンドは無い

// [左端, j)
assert_eq!([1, 2, 3], a[(Bound::Unbounded, Bound::Excluded(3))]);
assert_eq!([1, 2, 3], a[..3]); // ショートハンド

// [左端, j]
assert_eq!([1, 2, 3, 4], a[(Bound::Unbounded, Bound::Included(3))]);
assert_eq!([1, 2, 3, 4], a[..=3]); // ショートハンド

// [左端, 右端]
assert_eq!([1, 2, 3, 4, 5], a[(Bound::Unbounded, Bound::Unbounded)]);
assert_eq!([1, 2, 3, 4, 5], a[..]); // ショートハンド

renderの話になってきたので。
CC: @Yosshi999

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

たぁしかにlengthにすると最後部分の計算が面倒ですね…。
(chagpt君も全く同じ意見だった https://chatgpt.com/share/67319e1d-53b0-8008-8a27-1ebec0b8cef3

少なくともmin,max指定はダメそうですね!!
例えば将来秒指定するAPIができた時に詰みそう。

lengthで範囲を指定し間違えるとエラーになってくれますが、endの解釈を間違えると変な音が出るだけなので、そういうものかと品質の誤解になりえるかも。

まーーーでもこれだけ考えて決定打が出ないということは、わりとどちらも一長一短で決まりな気がしますね!!
とりあえず今のところ、endにしてドキュメント書くでも、lengthにするでもどちらでも良さそう!!
また時間置きますかぁ。

@qryxip qryxip requested a review from Hiroshiba November 11, 2024 22:25
Copy link
Member

@Hiroshiba Hiroshiba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!!

レビューリクエストありがとうございます!!

@qryxip qryxip merged commit 918f226 into VOICEVOX:main Nov 11, 2024
29 checks passed
qryxip added a commit that referenced this pull request Dec 1, 2024
`Synthesizer::render`の引数の範囲指定部分を次のようにする。

Rust API: `range: Range<usize>`
Python API: `start: int, stop: int`

以前の議論:
#870 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants