wiprog

C#, .NET, Scala ... について勉強したことのメモ

うつ患者の私がまとめる神経伝達物質、ホルモン、治療薬、サプリ

私はおととしの 10 月に中等度のうつ状態と診断され、治療を続けています。 うつはもちろん症状がとてつもなくつらいのですが、治療に用いる薬は副作用や依存性が強いものが多く、ただしく付き合うのが難しいです。 幸い私はかなり快方に向かっているので、何かの参考になればと思い飲んだ薬や体験した副作用、効果をまとめていきます。 最近は減薬に伴って(自己責任で)サプリも飲み始めたので、それについてもまとめてみます。 サプリについては、病気じゃないけどなんか元気ないな〜とかそういう感じの人にも参考になるかなと思います。

うつに関係する物質

うつの投薬治療は、関係する神経伝達物質やホルモンに働きかけるものが多いです。 これらの物質の濃度を適切にコントロールすることで治療していきます。 まずは基礎知識として、うつに関わりの深いものについてまとめていきます。

ドーパミン

ドーパミンは神経伝達物質のひとつで、喜びや快楽、意欲、学習などに深く関係しています。 ドーパミンの不足が暗い気分になる、やる気が出ない、集中力がない、などの症状に関係していると考えられます。

セロトニン

感情や生理機能の安定に深く関係しています。ドーパミンやノルアドレナリンなどの感情的な情報をコントロールし、精神を安定させます。 また、メラトニンのもとになります。 うつの治療はセロトニンへのアプローチが鍵になります。

ノルアドレナリン

注意や衝動、闘争、恐怖、興奮などに深くかかわります。交感神経を活性化し、体を活動的にする役割があります。 憂鬱な気分への対処について鍵になります。

メラトニン

体内時計や睡眠に関係します。うつや投薬の副作用による不眠が現れた場合は、メラトニンへのアプローチが重要です。

処方されたことのある治療薬

スルピリド(ドグマチール)

病院で最初に処方された薬です。 ドーパミンにはたらきかけることで、意欲や気分の改善をします。 もともとは胃潰瘍の治療薬として開発されたものなので、安全性は高いです。 また、比較的すぐに効果が出ます。

効果としては、特に意欲の部分が改善されたように感じます。

副作用としては、食物が胃にとどまる時間を減らす作用があるためものすごくお腹がすくことと、高プロラクチン血症を誘発するのでおっぱいが大きくなりました。また、高プロラクチン血症によって性欲が低下しました。

イフェクサー

SNRI といって、セロトニンやノルアドレナリンの再取り込みを阻害することで脳内での濃度を上げる薬です。 比較的新しく、良い薬のようですが、私の場合は低用量でも副作用が強く、服用を継続できませんでした。 副作用としては昼夜を問わない激しい吐き気と食欲の著しい低下がありました。吐き気が強すぎて夜も眠れませんでした。 SNRI はこのような副作用が出ることが多いですが、体にあっている場合は服用を継続することで慣れるようです。 服用期間が短いため、効果についてはわかりませんでした。

サインバルタ

イフェクサーと同じ SNRI です。他の病気の疼痛の治療にも使われるようです。 これも食欲低下の副作用がありましたが、体が慣れると高用量でも問題なくなりました。 サインバルタの服用を始めてから、だんだんと気分が安定する日が増えていったように感じます。 ノルアドレナリンへの作用が強いので、活発にはなるのですが、その反面夜は眠りにくくなりました。 不眠の症状は、短期的には睡眠薬による対症療法、根本的にはサインバルタを減らすまたはやめることで改善できるようです。

服薬してから血中濃度が安定するまでに時間がかかるので、効果が出るまでに数日かかります。 また、飲み忘れても血中濃度はゆっくりと低下するため、 1 回飲めなかった程度で著しく病態が悪化することはありません。 ただ、血中濃度がそれほど変化しないにも関わらず、「飲み忘れてしまった・薬を減らした」という意識を強く持つことによって、急に悪い症状が出ることがあるので、減薬や飲み忘れの際はあまり深く考えないことも大切です。

依存性がある薬のため、突然の服用中止は非常に危険です。 薬を増やしたり減らしたりする場合はゆっくりと細かい段階を踏みながら、専門医が状態を見ながら慎重に判断すべきであり、勝手に量を変えるのも危険です。

ロゼレム

サインバルタによる不眠の治療のために処方されました。 メラトニンに作用して睡眠の改善を促します。効果はソフトですが、かなり安全な薬です。私の場合は効果がありませんでした。

ベルソムラ

ロゼレムの次に処方された薬です。これも安全な薬ですが、効果はソフトです。効きませんでした。

フルニトラゼパム

いわゆるベンゾジアゼピン系の睡眠薬です。 ベンゾジアゼピン系睡眠薬の中でもかなり強力な薬で、一瞬で眠れます。 効果が非常に強力で使い心地がよいため、超短期的に、あるいは頓服的に、どうしても眠れなくて睡眠不足で困っているときのみ使用すべきです。 漫然と使用するとやめられなくなるリスクが非常に高いです。 私は生活習慣の改善やサインバルタの減薬と合わせて、低用量を週に 2 〜 3 回頓服的に使用し、現在は服用をやめました。

レイプドラッグとして有名でアルコールとの併用が非常に危険な薬のため、取扱には注意が必要です。 海外では所持自体が罪になることもあるようです。 外見は白い錠剤ですが、内部は濃い青の着色がされていて、飲み物に混入した場合に気づきやすいように工夫されています。

一番小さい錠剤は 1mg ですが、強すぎる場合は半分に割って飲みます。 割るときは、中央に線が入っているので、スプーンの裏の中央に当てて、両端を親指で押すようにすると簡単に割れます。 割ると中身が真っ青でびっくりしますが、上述のとおりそれが正常です。

サプリ

摂取したサプリも紹介します。私は自己責任でやっていますが、うつの治療中は治療の妨げになったり、副作用が出現することもあるため、医師に相談することが望ましいです。

GABA チョコレート

睡眠の改善をうたっていますが、そもそも GABA は経口摂取をしたところで血液脳関門を通過できないため、プラシーボ以上の効果はないようです。 通常のチョコレートと同様に、チョコレートとしての精神への効能はあると思います。

トリプトファン

海外ではうつの治療に使用されることもあるようです。 セロトニンやメラトニンの前駆体として重要ですが、食事からは少量しか摂取できないようです。 飲み始めてから減薬もあまり苦ではなくなり、睡眠も改善されてきたように感じます。 高用量の SSRI や SNRI を使用している場合、セロトニン再取り込み阻害作用との相乗効果でセロトニン濃度が上がりすぎ、セロトニン症候群になる可能性があるようなので注意しましょう。

肝臓エキス + オルニチン

これは神経伝達物質やホルモンへの影響はあまりないと思いますが、うつの治療薬は肝臓への負担が大きいために服用しています。 飲み始めてからまだ血液検査をしていないのですが、目覚めがすっきりし、だるさもなくなったような感じがあります。

亜鉛&マカ

性欲の低下や活力に効果がありました。 女性の場合はどうなのかわからないです。

チロシン

ドーパミンやノルアドレナリンの産生にかかわります。 飲み始めて日が浅いですが、同時にドグマチールの服用も減らしており、減薬の影響を抑えられているように思います。 ドグマチールのせいでお腹が空きやすくかなり太ったので、チロシンを摂取することでドグマチールを辞められたらいいなと思っています。

さいごに

うつの治療は薬だけに依存せず、習慣や食生活、環境の改善、十分な休息(休息については、最初は過剰と思えるぐらいが良いと思います)、考え方や人生観の見直しを並行しておこなうことでうまく行きやすいと思います。 薬の服用は怖いですが、無理に減薬すると結局悪化して全体でみるとかえって長い治療期間がかかったり、病状がさらに悪くなることもあるので、医師とこまめに相談しましょう。 また、医師との相性もかなり重要なため、信頼できる医師を探すことも重要です(ゆうメンタルクリニック 上野院の石黒先生はすごく私にとっては良かったです。金曜日しかいらっしゃらないので予約は取りにくいと思いますが)。 思いつめ過ぎず、一度休職・休学や時短勤務等をして、思い切って休息や遊びに振り切ることも非常に大切です。

2019年をふりかえって。ピアノをやめた。

今年も無事終わりそうなので振り返ってみます。

2019 年、26 歳になって、生まれて初めてピアノをやめました。今まで見ていた世界が全部崩れて、新しい世界に出会ったみたいです。

赤ちゃんのころから音の出るおもちゃが好きで、耳が良かったのか歌もすぐ覚えるし、まだ立てるかどうかのときから、知らない大人ともペラペラ喋っていたそうです。 4 歳のころ、保育所の先生の猛烈な後押しに両親が説得されてピアノを習い始めました(父親は、男にピアノなんて、と反対したそうです)。 それから 26 歳まで、ぼくの人生にはつねに音楽が溢れていました。 大切なことは全部音楽が教えてくれました。音楽がない人生なんて考えられませんでした。

最初、練習することなんて知りませんでした。 実家は田舎の漁師で裕福な家庭ではなかったので、ピアノは買ってもらえず、どこからか譲り受けてきた古いオルガンを与えられました。 ぼくにとって、オルガンは練習するための楽器ではなく、最高に楽しい遊び道具でした。 家ではレッスンで与えられた曲は一切練習せず、好き勝手に弾いていたようにおもいます。 知らないうちに絶対音感というものがついていました。いまでも、楽音はほとんどすべてドレミで聞こえます(シャープやフラットに関してはすこし濁ったようなイメージで入ってきます)。 教本にのっている曲で、聞いたことのある曲なら練習しないで弾けました。 そうでない曲は、先生がレッスンでお手本を弾いてくれるので、それを聴いて覚えて弾きました。

オルガンはかなり古かったので、小学校に上がる前に壊れたとおもいます。 次は、小さい鍵盤が光るキーボードが家にやってきました。 キーボードはこれまたぼくの新しい遊び道具になりました。よくエレクトーンごっことか、木琴ごっことかをしていました。

そのうち、(どうやって習ったか覚えていませんが)少しずつ楽譜が読めるようになりました。 楽譜が読めるようになると、ピアノに触らなくても、頭の中で楽譜通りの音を再生できるようになりました。 そんなこんなで初見でも勝手に指が動くような状態になり、中学校に上がるぐらいまでは練習なんてほとんどしませんでした。 レッスンで初見で弾いてちょっと直されるか丸をもらって帰ってくる、というのがふつうでした。 小学生のとき(時期は覚えていない)両親は金銭的な悩みもあったでしょうが、一番安い中古のアップライトを買ってくれました。 楽器としては決してスペックがよいとはいえないピアノでしたし、音色もあまり良いものではありませんでしたが、それでも、初めて買ってもらった生の楽器には、思い出がたくさん詰まっています。

中学生にもなると、古典のソナタやバッハの勉強に入って、さすがに初見ではつらくなり家で練習するようになりました。 練習の楽しさがわかると、もっと勉強したいとか、そんなふうに思うようになりました。 他に特別な取り柄もなかったので、人に認められる手段として、音楽以外考えられなくなっていました。 中学校後半になると、変声障害によって人と話すのが嫌になりました。 毎週遊んでいた友達とも疎遠になりました。 そんなとき、感情をぶつける唯一の相手はピアノでした。 このときから、どんどん音楽にのめり込み、ピアノだけがぼくの唯一の理解者となっていました。

中学を卒業する頃のぼくは、それなりに賢明な選択をしました。 まじめに頑張って勉強すれば就職して食べていけることがほとんど保証されているような、釧路高専に入りました。 声のこともあって、音大に行きたい、ピアニストになりたいなんて夢はそっと殺しました。 歌の試験があることぐらい、中学生のぼくでもわかっていました。

高専に入ってから、少ないながらも友達はできました。 でも、遊びには参加しませんでした。意図的に深くかかわらないように逃げました。カラオケが嫌でした。歌うのも嫌だし、気を遣われるのも嫌でした。 寮にも部活にも入っていなかったので、人とのつながりは最低限のものでした。

吹奏楽、というのはなんとなく聞いたことがある言葉、ぐらいのものでした。 母親や叔母が学生時代にやっていた。高校の時の先生がなんとか先生っていう有名な先生だった。とかそのぐらいの認識でした。 入学当初はスルーしていましたが、夏の吹奏楽コンクールまで 1 ヶ月切った頃でしょうか。 ミス・サイゴンを演奏するのに、あの印象的なピアノを弾く部員がいないようでした。 部活の掲示板に「ピアノ急募!!」と書かれているのをみました。 そこで、思い切って吹奏楽部に入りました。

吹奏楽部に入ってから、とりあえず人手のたりない打楽器に配属されました。 とにかく練習しました。先輩たちや同期はみんな優しく接してくれました。 音楽の力で、人とつながるという経験をしました。 みんなでひとつの音楽をつくりあげる楽しさも知りました。 頑張っていれば、周りが助けてくれる、ということも学びました。 集団で生きることの難しさも学びました。

音楽の面でも大変な学びを得ました。 顧問の先生は素晴らしい先生でした。音楽に情熱をもち、音楽を深く学ぶことの楽しさを教えてくれました。 毎年、冬になるとソロコンテストの伴奏をしていました。 ここでは、ふだんのピアノのレッスンでは触れることのできないフランスの現代曲なんかにも触れることができました。

どんどん音楽が好きになりました。

でも、どんどん音楽の世界に閉じ込められていきました。

高専に在学中、金銭的な問題から、ピアノをやめるように両親に何度も言われました。 ピアノを辞めることは、当時のぼくにとって、声を奪われるのと等しい苦痛でした。 泣いてしがみついて続けさせてもらえるように頼みました。 今思えば、両親としては、金銭的な理由以外にも、音楽に依存してしまったぼくを外の世界に出したい、というのもあったのかもしれません。

高専での 5 年間はあっという間に過ぎていきました。 卒業式では泣きました。 就職したら音楽がいままでのようには続けられないということはわかっていました。

2013 年に就職してから、少しだけ音楽と距離ができました。 それでも、中古の安い電子ピアノを家に置いて毎日弾き(苦情が来ました笑)、週末にはレッスンに通いました。 作曲の先生を紹介してもらって、作曲の指導も受け始めました。 音大も目指しておらず、ただ好きでやっているだけだから、という理由で、作曲のほうはレッスン料なしで、和声や対位法や作曲についてたくさん教えていただきました。 作曲をしたり、それを発表したり、高専時代の同期(フルートが上手な女の子でした)といっしょに演奏活動をしたりするうちに、どうしても東京でピアノを頑張ってみたい、という気持ちになりました。

生活を徹底的に切り詰め(ごはんが 1 食りんご半分だったり、ごはんに醤油をちょっとかけただけだったりしました)上京資金を 150 万円ほどためたぼくは、 2 年半勤めた会社を辞め、2015 年の秋に東京にやってきました。 東京にやってきたぼくは、 IT エンジニアとしての就職先(現在の職場)を見つけるやいなや、まず防音マンションの契約料に 30 万円ほど、のこりの 120 万円は全額グランドピアノの購入資金にあて、家具の購入は必要最低限に抑えてリボ払いでなんとかしました。 今考えたら狂っています。

上京前にピアノの先生も見つけていたぼくは、上京後すぐにレッスンに通い始めました。 東京でのレッスンは、いままでうけてきたものとは質がちがいました。 1 時間のレッスンで数小節も進まない、そんなレッスンをはじめて経験しました。 コンクールにも参加して、最初の年は全国大会に出場しました。 このことが、のちのちプレッシャーになっていきます。

上京後の生活は決して楽ではありませんでした。 厳しいレッスン、慣れない仕事、決して高くない給料、ピアノの維持費、防音マンションの家賃、レッスン代やコンクール、演奏会の参加料。 いつもギリギリで生活していました。

ある日、本番で失敗をしました。その年、全国大会にはいけませんでした。 ふっと、糸が切れました。 金銭的にも精神的にも肉体的にも、すべてが限界でした。 病院に行くと、中等度のうつ状態と診断されました。 そのときからピアノはおやすみしますと伝え、会社も休職することになりました。 2018 年末のことでした。

休職中は実家に帰りましたが、北海道では心療内科が十分になく、転院がままならない状況だったので 2 週間に 1 度上京していました。 このころ、ピアノはほとんど触りませんでした。 2 ヶ月ほどたって、回復したぼくは職場に復帰しました。

このとき、気づいてしまいました。ピアノを弾かなくても生きてていいんだって。

2019 年春のある日、整体に行きました。 首がものすごく歪んでいるとのことで、骨格の調整をされました。 直接の関係はわかりませんが、この頃から、以前に増して声の違和感がありました。 でも、なんだかちょっとずつ低い声が出るようになって、気づいたら普通の声になっていました。

気づいたら、あらゆることを自由に表現できる声を手に入れていました。 長年身につけていた重い鎖がほどけて落ちていきました。

そこから、音楽に対する情熱が一気に失われました。 声が出るのに、ピアノを弾く意味がわからなくなりました。 またピアノを弾いて、追い詰められるのが怖くなりました。

そうして、 2019 年はほとんどピアノを弾かずに過ごしました。

嫌いになったわけではありません。ただ、必要がなくなってしまいました。 これまではずっと、生きるためにピアノが必要でした。でも、ピアノがなくても生きられるようになってしまいました。

今はまだ、ピアノを弾く意味を見つけられていない状態です。 これから長い年月をかけて、少しずつ音楽に向き合って、情熱を取り戻していけたら。。そんなふうに思っています。

async な lock をしよう

Qiita から移行

--

C# では,非同期なメソッドでは lock が使えません.この記事ではそれでも lock したいときはどうするのっていうお話をします.

たとえば,こんなふうにダブルチェックロッキングしたいとしますね.

// これが複数のスレッドから非同期に呼ばれる
private static async ValueTask 条件満たしたらなんかするAsync()
{
    if (条件)
    {
        lock (_lockObj) 
        {
            if (条件)
            {
                await なんかすごく重いIOバウンドなやつAsync();
            }
        }
    }
}

lock ステートメントは非同期メソッド内で使えないので,実際にはこのコードはコンパイルが通りません. じゃあどうしようか,といって,一番ラクな逃げ道は同期にしちゃうことです.カンタンカンタン.

// これが複数のスレッドから非同期に呼ばれる
private static void 条件満たしたらなんかする()
{
    if (条件)
    {
        lock (_lockObj) 
        {
            if (条件)
            {
                なんかすごく重いIOバウンドなやつAsync().Wait();
            }
        }
    }
}

Task だって Wait しちゃえばただの同期,これはちゃんと動きます.でもここで「いやなんのための async なんだよ」ってなりますよね. lock したいだけなのに,そのために非同期の恩恵を捨て去る,そんなことやっちゃダメです.

じゃあ最新技術をこね回して難しいコード書くのかっていうとそんなことはなく,むしろ全く逆で古くからある技術を使います.そう,セマフォ です. セマフォっていうと OS の機能で,プロセス間の資源のアクセス制御に使うイメージですが,.NET にはプロセス内で利用するための SemaphoreSlim クラスがあります. セマフォといっても結局待たなきゃいけないでしょって話なんですが, SemaphoreSlim クラスには非同期で待てる WaitAsync() メソッドがあるわけです. これをつかうと完全に非同期な lock が実現できるわけですね. これはもうパターンが固定なので,ちょっと汎用的に AsyncLock なんていうクラスを作っておくとどこでも使えます. やってることもとてもカンタンなので,作り方さえ覚えてしまえばとっさのときにも書けます.

/// <summary>
/// async な文脈での lock を提供します.
/// Lock 開放のために,必ず処理の完了後に LockAsync が生成した IDisposable を Dispose してください.
/// </summary>
public sealed class AsyncLock
{
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

    public async Task<IDisposable> LockAsync()
    {
        await _semaphore.WaitAsync();
        return new Handler(_semaphore);
    }

    private sealed class Handler : IDisposable
    {
        private readonly SemaphoreSlim _semaphore;
        private bool _disposed = false;

        public Handler(SemaphoreSlim semaphore)
        {
            _semaphore = semaphore;
        }

        public void Dispose()
        {
            if (!_disposed)
            {
                _semaphore.Release();
                _disposed = true;
            }
        }
    }
}

面倒な人はこれをコピペでいいです.使うときは, lock 構文の代わりに using 構文を使います.これによって, semaphore の管理を忘れて lock という意味を持たせた見た目のコードを書けます.

たとえば,最初の例を書いてみるとこんな感じです.

private static readonly s_lock = new AsyncLock();
private static async Task 条件満たしたらなんかするAsync()
{
    if (条件)
    {
        using (await s_lock.LockAsync()) 
        {
            if (条件)
            {
                await なんかすごく重いIOバウンドなやつAsync();
            }
        }
    }
}

lock が using に変わっただけで,あとはあまり変わりません.でもこれはコンパイルも通るし,ちゃんと非同期でパフォーマンスよく動きます.

というわけで,いままで .Wait しちゃってた方,今日からは await しましょう!

Span<T> のつかいみち

Qiita から移行 (2019/12/10 投稿)

--

これは、 C# Advent Calendar 2019 の 10 日目の記事です(遅刻すみません!)。 前の記事は、 @Xeltica さんの C# 用ゲームエンジンを自作した話 です。

.NET Core 2.1 で使えるようになってしばらくたった Span<T> ですが、まだ使えてないよ〜って C#er のみなさんもまだまだいらっしゃると思うので、ぼくが書いたコードを晒していきます。 もっと速くなるよ、とかあれば教えてください!

文字列系

ここでのコツは、 Span<char>#ToString() をうまく使うことと、 stackalloc でスタック領域を活用することです。 ヒープアロケーションを極力避けることで、高速に動作させます。

byte[] -> 16 進数 への変換

byte 配列を 16 進数の文字列に変換する拡張メソッドです。

/// <summary>
/// 16 進数の文字列に変換します
/// </summary>
public static string ToHexString(this byte[] source, bool upperCase = false)
{
    Span<char> buffer = stackalloc char[source.Length * 2];
    source.WriteAsHexChars(buffer, out _, upperCase);
    return buffer.ToString();
}

/// <summary>
/// バイト列を 16 進数の列として書き込みます
/// </summary>
public static void WriteAsHexChars(this ReadOnlySpan<byte> bytes, Span<char> dest, out int charsWritten,
    bool upperCase = false)
{
    charsWritten = 0;
    
    foreach (byte b in bytes)
    {
        b.WriteAsHexChars(dest.Slice(charsWritten), out var count, upperCase);
        charsWritten += count;
    }
}

/// <summary>
/// バイトを 16 進数として書き込みます
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteAsHexChars(this byte b, Span<char> dest, out int charsWritten, bool upperCase = false)
{
    ReadOnlySpan<char> source = upperCase ? s_byteToHexUpper[b] : s_byteToHexLower[b];
    source.CopyTo(dest);
    charsWritten = source.Length; // 2 固定のはず
}

// byte -> char[] に変換するための 配列
private static readonly char[][] s_byteToHexLower =
    Enumerable.Range(0, 256).Select(x => ((byte) x).ToString("x2").ToCharArray()).ToArray();

private static readonly char[][] s_byteToHexUpper =
    Enumerable.Range(0, 256).Select(x => ((byte) x).ToString("X2").ToCharArray()).ToArray();

snake_case -> PascalCase への変換

スネークケースからパスカルケースへの変換をします。これは他の変換 (例: Pascal -> snake) にも応用できます。

public static string SnakeCaseToPascalCase(this string snake)
{
    ReadOnlySpan<char> snakeSpan = snake;
    Span<char> buffer = stackalloc char[snakeSpan.Length];

    int bufferPos = 0;
    bool toUpper = true;
    for (var i = 0; i < snake.Length; i++)
    {
        var target = snakeSpan[i];

        if (target == '_')
        {
            toUpper = true;
        }
        else
        {
            buffer[bufferPos++] = toUpper ? char.ToUpper(target) : target;
            toUpper = false;
        }
    }

    return buffer.Slice(0, bufferPos).ToString();
}

MD5 ハッシュの計算

MD5 ハッシュの計算にも Span が活用できます。 MD5CryptoServiceProvider はすこしラップしてあげると使いやすくなりますが、ここでも Span を活用します。 Span のコツは、できるだけ最後まで Span で扱うこと (= スタックを最大限に活用)、だと思ってます。 さっきの byte[] -> 16 進への変換も利用して、16 進数の書き込みまで Span だけでやってます。

/// <summary>
/// MD5 ヘルパ
/// </summary>
public static class Md5HashHelper
{
    // md5 インスタンスキャッシュ
    // このインスタンスはメソッド呼び出しによって内部状態が遷移するため
    // かならず内部状態の初期化を行うこと。
    // 呼び出し後に自動的に内部状態が初期化されるメソッドは ComputeHash, TryComputeHash のみです。
    [ThreadStatic] private static MD5CryptoServiceProvider t_md5 = null;

    /// <summary>
    /// MD5 ハッシュのバイト長
    /// </summary>
    public const int HashBytesLength = 16;

    /// <summary>
    /// MD5 ハッシュの十六進表記の文字数
    /// </summary>
    public const int HashHexStringLength = 32;
    
    /// <summary>
    /// キャッシュを使用するか
    /// </summary>
    /// <remarks>
    /// true の場合、 MD5CryptoServiceProvider のインスタンスをスレッドごとにキャッシュして使用します。
    /// false の場合は毎回新しい MD5CryptoServiceProvider のインスタンスを使用します。
    /// </remarks>
    public static bool UseCache { get; set; } = true;

    /// <summary>
    /// 指定したバイト配列のハッシュ値を計算します。
    /// </summary>
    public static byte[] ComputeHash(byte[] buffer)
    {
        return ComputeHash(buffer, 0, buffer.Length);
    }

    /// <summary>
    /// 指定したバイト配列の指定した領域のハッシュ値を計算します。
    /// </summary>
    public static byte[] ComputeHash(byte[] buffer, int offset, int count)
    {
        var md5 = RentUnsafe();
        var result = md5.ComputeHash(buffer, offset, count);
        ReturnUnsafe(md5);
        return result;
    }

    /// <summary>
    /// 指定した Stream オブジェクトのハッシュ値を計算します
    /// </summary>
    public static byte[] ComputeHash(Stream inputStream)
    {
        var md5 = RentUnsafe();
        var result = md5.ComputeHash(inputStream);
        ReturnUnsafe(md5);
        return result;
    }

    /// <summary>
    /// 入力バイト列のハッシュ値を計算し、出力バイト列にコピーします。
    /// </summary>
    public static bool TryComputeHash(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
    {
        var md5 = RentUnsafe();
        var result = md5.TryComputeHash(source, destination, out bytesWritten);
        ReturnUnsafe(md5);
        return result;
    }

    /// <summary>
    /// 入力バイト列のハッシュ値を計算し、16 進数表記で出力文字列にコピーします
    /// </summary>
    public static bool TryComputeHash(ReadOnlySpan<byte> source, Span<char> destination, out int charsWritten, bool upperCase = false)
    {
        Span<byte> buffer = stackalloc byte[16];
        if (TryComputeHash(source, buffer, out var bytesWritten))
        {
            ((ReadOnlySpan<byte>) buffer).WriteAsHexChars(destination, out charsWritten, upperCase);
            return true;
        }
        else
        {
            charsWritten = 0;
            return false;
        }
    }
    
    /// <summary>
    /// MD5 インスタンスを借用します。
    /// </summary>
    private static MD5CryptoServiceProvider RentUnsafe()
    {
        if (!UseCache || t_md5 == null)
        {
            return new MD5CryptoServiceProvider();
        }
        else
        {
            var md5 = t_md5;
            t_md5 = null;
            return md5;
        }
    }

    /// <summary>
    /// MD5 インスタンスをキャッシュに返却します。
    /// ※返却前に内部状態が汚染されたインスタンスを返却しないこと。
    /// </summary>
    private static void ReturnUnsafe(MD5CryptoServiceProvider md5)
    {
        if (UseCache && t_md5 == null && md5 != null)
        {
            t_md5 = md5;
        }
    }
}

Utf8Json のカスタムフォーマッタ

JSON のフォーマッタを書くときも Span を活用できます。

Enum を CamelCase にするフォーマッタ

PascalCase な enum メンバを CamelCase でシリアライズしたいことがあって書いてみました。

public class CamelCaseEnumFormatter<T> : IJsonFormatter<T>
    where T : struct
{
    public void Serialize(ref JsonWriter writer, T value, IJsonFormatterResolver formatterResolver)
    {
        var str = value.ToString();
        Span<char> buffer = stackalloc char[str.Length];
        str.AsSpan().CopyTo(buffer);
        buffer[0] = char.ToLower(buffer[0]);
        writer.WriteString(buffer.ToString());
    }

    public T Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
    {
        var str = reader.ReadString();
        return Enum.Parse<T>(str, true);
    }
}

おまけ: StringBuilder

これは半分遊びですが、スタック上で複雑な文字列を構築していきたいときのための、StringBuilder の Span 実装です。

public ref struct SpanStringBuilder
{
    private Span<char> _buffer;

    private int _index;

    public SpanStringBuilder(Span<char> buffer)
    {
        _buffer = buffer;
        _index = 0;
    }

    public void Write(ReadOnlySpan<char> str)
    {
        str.CopyTo(_buffer.Slice(_index));
        _index += str.Length;
    }

    public void WriteLine(ReadOnlySpan<char> str)
    {
        Write(str);
        Write(Environment.NewLine);
    }

    public void Write(char c)
    {
        _buffer[_index++] = c;
    }

    public void Write(int value)
    {
        if (!value.TryFormat(_buffer.Slice(_index), out var charsWritten))
        {
            throw new OutOfMemoryException("buffer のサイズが足りません");
        }

        _index += charsWritten;
    }

    public Span<char> AsSpan() => _buffer.Slice(0, _index);

    public override string ToString() => AsSpan().ToString();
}

読書メモ: 起業の科学 #1 スタートアップにとっての「良いアイデア」とは

いかに課題にフォーカスするか

解決する課題の質を高めよ

「スタートアップの生死を分けるのは、 Product Market Fit (PMF, 市場で顧客から熱狂的に愛される製品のこと) を達成できるかできないかだ」 - Mark Andreessen, Founder of Andreessen Horowitz

いくら優れたプロダクトを生み出しても、市場に受け入れられなければ成長はできない。 PMF を達成するためにはまず、自分たちのビジネスアイデアが市場から求められているものなのかを検証すること。 スタートアップにおいては 課題の質 にフォーカスすることが最も重要である。 スタートアップのアイデアは、 課題ドリブン (課題ありき) ではなく、ソリューションドリブン技術ドリブン であることがあまりに多い。

「バリューのある仕事をしようと思えば、取り組むテーマは イシュー度解の質 が両方高くなければならない」 - 安宅和人, CSO of Yahoo

バリューのあるアイデアを見つけるには、「課題の質を上げてから、ソリューションの質を上げる」というパスしかない。 よって、スタートアップを始めるにあたって真っ先に注力すべきは、解決を目指す課題の質を向上させることである。

  • 今検討しているアイデアは、顧客にとって本当に痛みのある課題なのか?
  • このアイデアの妥当な代替案が、既に市場に存在していないか?

のように、様々な角度からアイデアの深堀りを繰り返していくことで、課題の質が上がる。 それができたら、その課題に対する解決策を検討し、磨きをかけていくことで、初めて価値のある「良いアイデア」に至る。

課題を軽視して大失敗

課題の質ではなく、ソリューションの質にこだわると、顧客にはほとんど使われずに終わる。

グーグルグラスや Apple Watch は膨大な資金力とブランド力を背景に、 PMF ではなく力技を選んだ。 しかし結局、ユーザの期待に応えることはできず、思うようには売れなかった。

課題を意識しないのは自殺行為

先の例が示しているのは、 Google や Apple のような実績や資金力のある彼らですら、課題の質を軽視して、プロダクトありき、ソリューションありきで考えると容易に失敗するということ。 資金も人材も知名度もないスタートアップが課題を軽視することは、ただの自殺行為である。

課題の質を決める 3 つの要素

課題の質は、ファウンダー(創業者)個人が次の 3 つの要素をどれだけ持つかに依存する。

  • 高い専門性
  • 業界(現場)の知識
  • 市場環境の変化 (PEST: Political, Economic, Social, Technological) に対する理解度

これらが極めて重要であり、ファウンダー個人に求められる。

自分ごとの課題を解決せよ

課題の質を高めるもう一つの要素は、ターゲットとする課題が「自分ごと」であるかどうか。 Airbnb (創業者が家賃を払えないという切迫した状況から生まれた) や、ダイソン (従来の紙パックの掃除機の吸い込みの弱さやパックの交換が面倒で大きな憤りを覚えた) は、自分が痛みを感じている具体的な課題から始まり、その課題を自分ならどう解決するかという順番でビジネスアイデアが形成されている。

魔法のランプクエスチョン (もし魔法のランプがあって、課題を解決するためにソリューションを出してくれるとしたら、どんなものがいいか?) から導き出される答えの中に、ダイヤの原石があるはず。 自分ごとの課題といっても必ずしも本人が当事者である必要はなく、周りの身近な人が抱えている課題でも、その課題の痛みをしっかり理解しているのであれば OK。

課題の発見は、まだ「始まり」

課題の発見は始まりにすぎない。重要なのはその後の磨きこみである。 このときに自分ごとの課題でなければ、本気で磨きこみができない。

なぜあなたが、それをするのか?

スタートアップでは、 第三者の課題 (自分がそこまで共感や思い入れがない他人の課題) を解決しようとすることは避けなければならない。 強い共感が持てない課題は自分ごとにならず、痛みの検証がどうしても表面的になり、結果的に真の課題にたどり着くことが困難になる。 また、説得力もないので周囲からの協力も得られにくい。

「YC のインタビューでは『誰がその製品を心の底から欲しがっているのか?』を聞く。ベストの答えは起業家自身であることで、次に良いのは、ターゲットユーザをものすごく理解しているのがわかる解答だ」 - Sam Altman, President of Y Combinator

ファウンダーの課題に人は集まる

課題への強い共感や思いが、スタートアップのビジョン、ミッションに翻訳されていく。 プロダクトを市場に本格投入するまでのフェーズにおいて、ビジョンやミッションはスタートアップの最大の競合優位性になる。 ユーザ、起業メンバー、投資家はいずれもファウンダーが語るビジョンとミッションに惹かれて集まる。

スタートアップが歩む道のりが想像以上につらいことも、自分ごとの課題から始める必要がある理由である。 最初に立てた仮設はほぼ覆ってしまい、限りある資金がどんどん減っていく。 この時、ファウンダーが自分ごとの課題を持ち、将来的にこうあるべきだという強いビジョンやミッションを掲げていれば、高いレジリエンス(メンタルの回復力)の源になる。

課題に出会った原体験は何か

「自分ごとの課題になっているかどうか」を別の言い方で自問するなら、「その課題にストーリー(原体験)があるか」

誰が聞いても良いアイデアは避ける

アイデアは crazy か

誰が聞いても良いと思えるアイデアは、スタートアップにとっては選んではならないアイデアだ。 ネガティブなフィードバックが周囲から集まる状態を「マーケットが定義されていない状態」ととらえて、今のタイミングで事業を手掛けることがチャンスだと考える。

「一見アンセクシーだが、実はセクシーなアイデアを見つけることが決め手となる」 - George Kellerman, COO of Yamaha Motor Ventures & Laboratory Silicon Valley

例: 排せつ予測デバイス D Free

こうした crazy でアンセクシーなアイデアは、人に話すのが恥ずかしいはずだ。 人に話すのが恥ずかしい段階とは、その課題を言語化して説明するフレームワークを入手できていないために、人に伝えるのが困難だということ。 当然、その課題に目をつけている企業はまだ存在しないか、ごくわずかである。

一方、言語化して人に伝えられるような課題をターゲットにした場合は、既に課題が認識されており、妥当な代替案がある場合が多い。 このようなビジネスは投入できるリソースの勝負や価格勝負になるため、スタートアップが狙うのは賢明ではない。

「競争は負け犬がすることだ」 - Peter Thiel, Founder of PayPal

市場のシェアを激しく奪い合う消耗戦になると、リソースの多い大企業が圧倒的に有利になり、スタートアップに勝ち目はない。

大企業の意思決定の仕方

大企業が新規事業を始めるときは、取締役会でほとんどの役員が賛同しないと稟議の承認が下りない。 判断の際に役員が気にするのは課題の質などではなく、その事業がもたらす売り上げや利益の見通し、蓋然性、既存のコアビジネスと競合しないか、といった点である。 そのため、アンセクシーなアイデアには挑戦しにくい。

「スタートアップではハードなことをするほうが実は近道である。簡単な道を選ぶことは結果として遠回りになる」 - Sam Altman, President of Y Combinator, lecture in Stanford University

一見悪いアイデアが世界を変えた

crazy なアイデアで大成功を収めたスタートアップの代表格は Airbnb だ。 犯罪大国のアメリカで赤の他人の家に泊まる、他人を自宅に泊めるという行為は、まさにバッドアイデアそのものだった。

「多くの人が、 Airbnb はうまくいった最悪のビジネスアイデアだと言っている」 - Brian Chesky, CEO of Airbnb

Uber のように、だれもが当たり前だと思ってきたこと(タクシーを捕まえるために何分も手を挙げて待ち、英語が片言のドライバーに目的地を伝えるのも大変で、支払いは基本的に現金)に疑問を投げかけられるかどうかが、スタートアップが世界を変える存在になれるかどうかの最初の分かれ道になる。

他の人が知らない秘密を知っているか?

成功する人は、ほかの人が知らない秘密を知っている。 ここではインスタカート(スマホなどで注文すると近所のスーパーでの買い物を代行してくれるサービス)の例を見ていく。

インスタカートが大成功した理由

インスタカートは店舗と商品を選ぶと、「ショッパー」と呼ばれる一般人が、注文した人の代わりに店まで買い物に行き、 45 分以内に自宅まで届けるという仕組み。 創業者の Apoorva Mehta はアマゾンの物流システムを開発するエンジニアで、物流と小売りに関して高い専門性があり、市場の流れをつかんでいた。 しかし、彼自身が glossary shopping (食料品の買い物) は自分で行うもの、という通例に疑問を持った視点こそが重要。

インスタカートは経歴チェックやトレーニングプログラム、カスタマーレビューの仕組みを導入し、見知らぬ人に直接口委に入れる食料品の買い出しを頼んだり、住所を教えたりすることの不安を払拭した。 このサービスは全米に一気に広がった。

Mehta は Amazon 在職中に新規事業としてインスタカートのアイデアを提案したが、自社ビジネスと競合する奇抜な試みでしかないと却下された。 Amazon が膨大な資金を投資してきたインフラ (自社倉庫、外部の物流会社との契約) を否定することになるからだ。 この却下によって Amazon が現状のシステムでこのサービスに対応できないことが明確になり、Mehta はアマゾンを脅かすほどのビジネスチャンスがあると気付いた。

小売りとの win-win 関係

Amazon の台頭はスーパー等の小売店にとって脅威である(カスタマーの予算を奪い合う)が、インスタカートの場合は Win-Win の関係が成り立つ。これが Mehta が気付いた 秘密 である。

なぜ crazy なアイデアが求められるのか

crazy なアイデアが求められる背景には、 IT の進歩で、マーケットのパラダイムシフトが高速化していることがある。 2014 年ごろから、ユニコーン企業が続々と生まれるようになり、評価額の上昇カーブもすさまじいものになった。 この事実は、プロダクトやサービスの が短くなっていることも意味する。 これだけの速さだと、 First Mover (最初の市場参入会社) が出てきたのを後追いしても遅い場合があり、だからこそ、誰よりも先に PMF を達成することが重要である。 このときにベースになるのが、「一見すると悪いが、本当は良いアイデア」である。

イノベーションカーブの変化

従来のイノベーションカーブは、innovator (革新者) がいて、Early Adopter (新たに登場した商品、サービス、ライフスタイルなどを、比較的早期に受け入れ、それによって他の消費者・ユーザーへ大きな影響を与えるとされる利用者層) がいて、Chasm(深い裂け目、の意。Earyly Adopter と Early Majority の間の大きな溝) を超えるとようやく Early Majority (Early Adopter に追従する形で受容し始める利用者層)に到達する、といった話だった。 このイノベーションカーブであれば、ある程度の時間をかけてプロダクトを検証することができたが、スマホなどの普及による情報伝達スピードの加速で、このモデルは古くなっている。 現在は Trial Customer (新しもの好きの顧客) と Burst Majority (爆発的に広がる一般層) の 2 段階できわめて速く浸透する。 ユーザ数がある値を超えるのを待たず、一定数の Trial Customer のフィードバックをベースにプロダクトを磨きこむという新時代は、スタートアップにとっては好機ともいえる。

代替案のないアイデアを探せ

「新規事業を考えるときには無消費をターゲットにせよ」 - Clayton M. Christensen

無消費とは、顧客が何も持たない状態のこと。つまり、前例もなければ、既存の消費者もいない、代替案のない状態のこと。 こうした場所を発見して PMF を達成できれば、大きな成長を見込める。

ロイヤルティーループの劇的変化

Royalty loop とは、製品を知った人がそれを気に入り、ユーザとして定着して使い続けてくれるまでの流れを輪のような形で示したもの。

従来の Royalty loop は、 AIDMA(Attention - Interest - Desire - Memory - Action) モデルであり、ユーザは一つのプロダクトやサービスを選ぶために時間をかけて、それを使う価値があるかを検証していた。 最近はほとんどのサービスで、「最初の 1 カ月は無料」といったフリーミアムが定番化した。まず使ってみて、有用だと思ったら購入する。気軽に始め、使っていく中で本格的にユーザがそのサービスに定着する (他の代替案を捨てる) というあらたな Royalty loop が生まれた。

また、継続的に使用する顧客との接点が多いフリーミアムやサブスクリプションモデルの利点はユーザの囲い込みだけではなく、フィードバックを高速に集められるという点にもある。 それをもとにプロダクトを絶えず改善していくことで、ファンの量と質 (高い LTV、低い解約率) を増やしていくことができる。

スタートアップが避けるべき 7 つのアイデア

1. 誰が見ても、最初から良いアイデアに見えるもの

一見よさそうに見えるアイデアはすでに誰かが手掛け、たいていは失敗している。

2. ニッチすぎる

あまりにも市場がニッチすぎると、将来的な成長が見込めず、スケールしない。

3. 自分が欲しいものではなく、作れるものを作る

技術ドリブンではいけない。技術的に作れたから作っただけのプロダクトは、課題から生まれたものではないので市場に受け入れられない。

4. 根拠のない想像上の課題

クラウドファンディングなどを使うと、たまたま出したコンセプト動画が受けて必要以上の金額が集まり、後戻りできなくなるケースがある。 Customer Problem Fit を実現しないうちに、表面的に PMF を達成したような状態。 もしビジネスをスケールするつもりなら、どんな課題を解決できるプロダクトになっているかを検証すべき。

5. 分析から生まれたアイデア

一時期あふれかえってどれも失敗したグルーポンの模倣サービスは、市場を俯瞰して空いている部分を狙うというロジカルなトップダウンアプローチでビジネスを展開しただけで、スケールするストーリーや、ファウンダー自身の思いが欠けていた。

6. 激しい競争に切り込むアイデア

スタートアップは「競争を避けること」が戦略の第一義であると考えるべき。

7. 一言では表せないアイデア

「誰 (customer) の何 (課題) をどのように解決するか」を一言で表せないアイデアは磨きこみが足りない。