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

Add: getOrThrow / deleteOrThrowを追加 #2055

Merged
merged 9 commits into from
May 13, 2024

Conversation

sevenc-nanashi
Copy link
Member

@sevenc-nanashi sevenc-nanashi commented May 4, 2024

内容

MapにgetOrThrow / deleteOrThrowを追加します。

関連 Issue

(なし)

スクリーンショット・動画など

(なし)

その他

(なし)

@sevenc-nanashi sevenc-nanashi requested a review from a team as a code owner May 4, 2024 00:35
@sevenc-nanashi sevenc-nanashi requested review from Hiroshiba and removed request for a team May 4, 2024 00:35
src/vite-env.d.ts Outdated Show resolved Hide resolved
src/helpers/strictMap.ts Outdated Show resolved Hide resolved
@sevenc-nanashi sevenc-nanashi changed the title Add: MapにmustGet/mustDeleteを追加 Add: MapにgetOrThrow / deleteOrThrowを追加 May 4, 2024
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.

プルリクエストありがとうございます!!
なるほどglobalに書くことによって認識させるんですね!!

実装理由も書いてあるのめちゃくちゃいいですね。
それが解消されたら別の解決策が使えるかもしれない。


Mapにメソッドを破壊的に追加する場合、起こりそうな問題をちょっと列挙してみました。

  • 全てのライブラリのMapがこうなるので謎のバグが発生するかもしれない
    • 例えばMap.getOrThrowというメソッドがなかったら処理を追加する子クラスを定義されてたりとか(なさそうだけど可能性としてはある)
  • 初学者がこれをVOICEVOX独自のものだと認識できない
  • ランタイム?が異なるとコンパイルは通るのにコードが動かないことがある
    • 例えばelectronのメインプロセスからはメソッドが見えるけれども実行時エラーが出るはず
    • 他にもWorkerなどでも同じことが起こるかも

これらの問題を低減する方法をいくつか考えたのでコメントしていきます 🙇


@yamachu すみません、ちょっと熟練者としてアドバイスをお願いできると 🙇

今回のプルリクエストのような形を実装するのってどう思われますか・・・?
多分本来はやっちゃいけないレベルなんだろうとは思うのですが、メリットも結構大きいのでとても迷ってます。

代替案としてはメソッドを諦めてgetOrThrowという関数を実装する形があります。
個人的には、globalではなく明示的にインポートすれば使える、という形にできればリスクも減らせてメリットが上回るのかなと考えてます。
できないなら・・・・・・どっちだろう。。。

Comment on lines 32 to 37
declare global {
interface Map<K, V> {
getOrThrow(key: K): V;
deleteOrThrow(key: K): void;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

これが独自のものだと気づきやすいように、/** */でコメントを書いておくと良いかも

},
);

declare global {
Copy link
Member

Choose a reason for hiding this comment

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

ちょっと詳しくないのですが、このファイルをimportしたら見えるようにする事って可能だったりしますか・・・?
というのも現状でelectronのメインプロセスから使えないのに見えるようになってて問題だな~~と感じたためです。

(その場合はaddPropertyが何度も実行されることも考えないといけないかも?)

Comment on lines 610 to 611
const phrase = state.phrases.get(phraseKey);
if (phrase == undefined) {
throw new Error("phrase is undefined.");
}
const phrase = state.phrases.getOrThrow(phraseKey);

Copy link
Member

Choose a reason for hiding this comment

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

念のために将来revertするかもと考えて、このコミットでの変更は最小限にしておくのはどうでしょう・・・?
src/components/Sing/SequencerPhraseIndicator.vueの1つだけにしておくとか。。

@yamachu
Copy link
Member

yamachu commented May 7, 2024

prototype汚染はあまり良い印象がないですねー…
とは言え、確かに便利ではあるので異なる形でもいいので導入できると嬉しいですね。

Hiroshibaさんが代替案として挙げている getOrThrow という関数で実装するのが無難に良いのではないかなと思いました。

prototypeに対して影響を及ぼす方法は、そのモジュールをimportしただけでprototype汚染が走ってしまうため、予期しないタイミングで挙動が変わってしまうみたいなデメリットはあるのかなと。
またテストを実行する時に必ずそのモジュールをimportしておかないといけないという知識が暗黙的に生まれてしまいます。

もしもprototypeに影響を及ぼす方法を取るのであれば、将来的にMapにgetOrThrowが生えて(ほぼほぼないとは思うのですが)くる未来を考慮して、

export const getOrThrow = Symbol();
Object.defineProperty(Map.prototype, getOrThrow, {
        value: (key) => {
            if (!map.has(key)) {
                throw new Error('key not found');
            }
            return map.get(key);
        },
        writable: false,
        enumerable: true,
        configurable: false,
    });

//

import { getOrThrow } from '@lib/StrictMap';

const a = new Map();
a[getOrThrow]("a") // throw new Error('key not found')...

みたいにSymbolを使う形にするのであれば、まだいいかな…とは思いました。(それでもimport時に副作用が発生するので、prototype汚染を行う箇所をuseStrictMapみたいな名前の関数として切り出して、entrypointで呼び出す、みたいなののをして、アプリケーションの初期化の中に含めてVOICEVOXのアプリケーションの知識として加えるとかは必要になります)

他にも、

interface StrictMap<T, K> extends Map<T, K> {
    getOrThrow: (key: string) => K;
}

export const createStrictMap = <T, K>(params): StrictMap<T, K> => {
  const map = new Map<T, K>(params);
  Object.defineProperty(map, 'getOrThrow', {
        value: (key) => {
            if (!map.has(key)) {
                throw new Error('key not found');
            }
            return map.get(key);
        },
        writable: false,
        enumerable: true,
        configurable: false,
    });
  return map;
}

//

import { createStrictMap } from '@lib/StrictMap';

const a = createStrictMap(new Map()) // とか createStrictMap([['a', 1], ['b', 2]]);
a.getOrThrow('a'); // 1
a.getOrThrow('c'); // throw new Error

みたいに、StrictMap 型を作ってしまう、みたいのは一つの手かもしれません(この場合、immerを経由した場合どうなるんだろうみたいのは試していません…)

と、方法はいくつかありますが、各種引数の型などを変えずに使う側でthrowするかどうかを選べる関数としての提供がいいんじゃないかなぁとは思いました(型として提供してあれば、必ず値があるというのを明示できるのでそれはそれで良さがありますが)

@Hiroshiba
Copy link
Member

@yamachu ありがとうございます!!!

暗黙的なimport順序というかルールが出来上がるというの、気づけてませんでした。。
ドキュメント整備や予期せぬ不具合などが起こり得るので、メンテコストが上がるリスクがありますね・・・・。


discordでもいろいろ教えてもらいました 🙇
https://discord.com/channels/879570910208733277/893889888208977960/1237377288333561957

アイデアとリスクをまとめるとこうでした。

  • MapにgetOrThrowメソッドを追加(いただいたPRの形)
    • importしてなくても使えるように見えてしまい、その案内をしっかりしないとなのでメンテコスト増加&謎エラーのリスク
  • MapにsymbolでgetOrThrowメソッド追加( @yamachu さんのコメント)
    • 書式が難しく、TypeScript慣れしてない方にややこしそう
  • getOrThrow関数定義
    • メソッドじゃないのでコード書くときに関数名思い出さないといけない

リスクとリターン考えると、一番バランス取れてるのはgetOrThrow関数なのかなと思いました 🙇
もしよかったら @sevenc-nanashi さんの意見も聞けると。。。 🙇

@sevenc-nanashi
Copy link
Member Author

まぁgetOrThrowが1番丸いと思います。

@sevenc-nanashi sevenc-nanashi changed the title Add: MapにgetOrThrow / deleteOrThrowを追加 Add: getOrThrow / deleteOrThrowを追加 May 8, 2024
src/helpers/mapHelper.ts Outdated Show resolved Hide resolved
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!!!

色々な調整ありがとうございました!!
なんだかんだ関数でも使いやすそうに感じました!

議論に探してくださった方もありがとうございました、非常に勉強になりました 🙇

@Hiroshiba Hiroshiba merged commit 595d108 into VOICEVOX:main May 13, 2024
8 checks passed
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.

4 participants