鬱になりました

鬱になりました。

TwitterFacebook にはちらっと書いたのですが、で1月終わりから休職しています。ここらで一度ふりかえってみようかと思ったので書いてみます。

f:id:kakkun61:20200904160823j:plain

kokoro.mhlw.go.jp

何かおかしい

何かおかしいなと思ったのは正月の帰省から戻ってきた後でした。買い物に行きたいなと思いつつ全然部屋から出る気が起きず4日ぐらい1日スパゲッティ1食みたいな生活でした。元々出不精とはいえ出たいと思いつつ出れないのが続いてるなと思っていました。

その後しごとが始まりそれなりにしごとをしていましたが、朝の起きれなさ*1と起きても倦怠感や外に出たくなさがあり在宅勤務をする日が増えてきました。在宅勤務でしごとがはかどればよいのですが、今いちしごとに手が着かず週次の進捗報告で報告する進捗が減っていくのを感じていました。

さすがにこれはしごとをやっているとは言えないなという気持ちになり、出社する足で会社に行かずに精神科病院に行きました。

精神科病院が家の向かいであることはさいわいだったと思います。そうでなければグダグダと行く予定を先延ばしにしていたはずです*2

診察後上司にチャットでしばらく休職したいむねを報告しました。

休暇 1ヶ月め(2月)

最初の1ヶ月は積極的なことは何もせず休養をしていました。

意欲を完全に喪失していたので趣味のプログラミングもせずひたすらビデオを見ていました。『エウレカセブン』や『おジャ魔女どれみ』を全話見たりしました。

意欲を失った結果夜起きていてもしかたがないのでこのころ睡眠の周期がよくなりました。しかし日中もよく寝ていました。

この時期は意欲の喪失だけでなく思考能力もかなり落ちていました。プログラミングをしなかったのは意欲低下だけでなく思考能力の低下も理由にあります。

1ヶ月も休んだしそろそろ復職するかと人事や産業医に伝えたところ、まだ休職できるので十分休養しなさいということで休職を続けることにしました。後から思うと自分でもまだまだ休養すべきだったと思います。

休暇 2・3ヶ月め(3・4月)

鬱になってから気付いたのですが自分の場合心が凹むと部屋が乱雑になってくるようです。特に台所にそれが一番初めに表れます。心の凹みは注意しないと見逃してしまうので台所の乱雑さは自分の指標だと思うようになりました。

意欲が少しずつ回復してきたので暇を感じるようになりました。また少しは先のことを考えるようになりました。医者に相談したところ復職支援・再発防止のリハビリテーションがあるということでそれに参加するようにしました。それはリワークと呼ぶそうです。最終的には週に5日リワークに通うことが1つの目標です。

リワークでは、詳しいことは分かっていないのですが、認知再構成法や認知行動療法問題解決療法といった手法を学んだり、他の参加者とボードゲーム*3をすることを通じて、思考や対人コミュニケーションの訓練をしたりします。またヨガや創作活動によって悩みなどを一時的に忘れるようにしたりします。

趣味の面ではこのころ鉄道模型ジオラマを作っていました。手を動かす作業をするとイキイキした感じがしました。以前から気になっていた 3D プリンターをジオラマに使いたくなったので光造形式 3D プリンターを購入しました。いろいろとしていましたがプログラミングはまだひかえていました。

どれくらいプログラミングができていなかったのかプログラマーには分かりやすい図だと思うのですが GitHub のアクティビティーの図を下に示します。3月にちょろっと活動があるのは 3D モデルを GitHub にアップロードしたからです。

f:id:kakkun61:20200904140104p:plain

休暇 4・5ヶ月め(5・6月)

このころになるとちょこちょこプログラミングをし始めるようになりました。そしてその結果夜更かしが増えました。これはいけません。プログラミング、うまく問題が解決できるととても快楽を感じるのですが、なまじそれを知ったがためにもうちょっとでできそうというような気持ちでついつい夜更かししてしまいます。これはしごとでもそうで夕方から気分が乗ってきた結果、職場の電灯が消える時間までやってしまう傾向がありました。これは、直すべきだとこの休養中に認識したことの1つです。

夜更かしについてもそうですが、リワークの中でストレスを受けた状況をふりかえることがあります。そこで自分が課題であると認識したことの1つに対人コミュニケーションに苦手意識があるということです。しごとやその他において何か相談ごとがあるときに、うまく人に話しかけられない、話しかけるのに苦痛を感じるというのがあります。よくよくふりかえると今回の休職の原因にも関係しているように思えています。これは長い付き合いになると思いますがうまくやっていけるよう克服したいと思っています。いろいろと仮説や対策などを考えているのですが長くなるのでここでは割愛しましょう。

金銭面ですが、かなり貯金を切り崩してきました。傷病手当で給与の3分の2に相当するものがもらえるのですが該当期間の3・4ヶ月おくれということもあって現金がゴリゴリと減っていきました。生活するだけでかなりお金が必要なのだなと実感します。一部株式を手放しましたが、COVID-19 で一時下がっていた株価が持ち直していたので少し助かりました。

休暇 6・7ヶ月め(7・8月)

どうも気持ちには波があるようです。この時期は負の時期で睡眠周期が過去にないほど崩壊しました。昼夜逆転だけでなく長時間寝たり短時間を1日に何回も寝たりといったぐあいでした。そんな状況だったのでリワークにもほとんど通えず、進捗リセットという感じでそのことに凹みます。傷病手当申請と同時に会社に生活記録を提出する必要があるのですが、こうした時期は事務しごとがなかなか進まず申請が2ヶ月ほど遅れてしまいました。その影響でまた現金がギリギリです。

休職できる期限があるのでいつまでも休職するわけにはいかないのですが、復職するまでにまだ克服したいことが残っているのでしばらくはその訓練をすることになりそうです。

まだ書くことがあった気がしますが、公開しないと思い出せそうにないので公開してしまいます。

最後に

物的支援をよろしくお願いします!

ウィッシュリスト(食品・日用品)

ウィッシュリスト(趣味)

*1:元々睡眠障害をわずらっていましたが。

*2:普段から散髪など基本的に外出予定はズルズルと先延ばしになるので。

*3:お邪魔者』がおもしろかったです。

一番簡単な MonadFail インスタンス

The English version is at Dev.


導入

failMonad から剥がされて早や幾年、私は失敗する可能性のある計算は MonadFail を使って型を付けるのが好きです。

foo :: MonadFail m => m a

こうすると IO の文脈であればその中で、純粋な文脈であれば Maybe などで具体化して呼ぶことができます。

-- IO の文脈では
foo :: IO a

-- 純粋な文脈では
foo :: Maybe a

さて、純粋な文脈として Maybe を使うと失敗のメッセージを失ってしまうことが嬉しくありません。では、Either を使えばいいのではないでしょうか?実は EitherMonadFailインスタンスになっていません。提案はされていますが、失敗・成功以外に同列にパラメーターを扱うケースもあるのでそのときに MonadFail であることは適切でないからです*1

gitlab.haskell.org

そういうわけで一番簡単な MonadFail インスタンスとして次のような Result 型が欲しくなりました。

newtype Result a = Result (Either String a)

instance MonadFail Result where
  fail = Result . Left

実をいうとこれに相当するものはすでにあるのですが非推奨となっています。それは mtl パッケージの ErrorT です。

either-result パッケージ

そういうわけで Result に加えいくつかの関数をまとめてリリースしたのが either-result パッケージです。

hackage.haskell.org

実際には Resultモナドトランスフォーマー版の ResultT を使って実装され、ResultT は transformers パッケージの ExceptTnewtype です。

type Result a = ResultT Identity a

newtype ResultT m a = ResultT (ExceptT String m a)

ResultTExceptT と異なるのは MonadFail インスタンスで、fail を呼ぶと ResultTLeft でくるむのに対して ExceptT ではベースのモナドfail を呼びます。ですので、ResultT ではベースのモナドMonad しか要求しませんが、ExceptT では MonadFail であることを要求します。

instance Monad m => MonadFail (ResultT m) whereinstance MonadFail m => MonadFail (ExceptT e m) where

モナドトランスフォーマーにしたついでに mtl の MonadError インスタンスにもなっているので throwErrorcatchError することができます。

exceptions パッケージは?

MonadThrow という型クラスもなかったっけ?はい、あります。exceptions パッケージ*2MonadThrowManadCatch 型クラスがあります。こちらは投げる・捉えるものが Exception 型クラスであることを要求します。使い分けとしては、投げる・捉えるものを型で区別したい場合は MonadThrowMonadCatch にして、単にメッセージのみでよい場合は MonadFail にすればよいと思います。

class Monad m => MonadThrow m where
  throwM :: Exception e => e -> m a

class MonadThrow m => MonadCatch m where
  catch :: Exception e => m a -> (e -> m a) -> m a

class Monad m => MonadFail m where
  fail :: String -> m a

まとめ

  • 定義時、失敗する計算は MonadFail m => m a にしよう
  • 使用時、IO などの文脈では IO a などとして使おう
  • 使用時、純粋な文脈では Result a として使おう
  • GitHub リポジトリーの Star というボタンを押そう

*1:Rust ではデフォルトで Result 型で同列に扱いにくいため Either という名前がよかったという主張もあるみたいですね。

*2:使用する場合は safe-exceptions パッケージをおすすめします。

フィルムの在庫を数えた

SARS-CoV-2 の影響やらで最近フィルムの消費が少ないので期限切れフィルムを洗い出すことと冷蔵庫の中の整理のためにフィルムを全部出して数えてみた。

続きを読む

Twitter ツイートにウェブアプリを埋め込む

Twitter Card の Player だと iframe を使って埋め込めることを知ったので埋め込んでみた。(ツイートの埋め込みの中で埋め込んだものを再生できないので一旦ツイートを開く必要がある。)

f:id:kakkun61:20200728163346p:plain

Player Card を使ってるのはよく見るのは YouTube だろう。WebGL を埋め込んでる人もいた。

実装

実装としてはメタタグを埋め込むだけ(diff)。

    <meta name="twitter:card" content="player" />
    <meta name="twitter:site" content="@kakkun61" />
    <meta name="twitter:title" content="Photo Film Dev" />
    <meta name="twitter:description" content="This is a timer app for developing monochrome negative photograph films." />
    <meta name="twitter:image" content="https://photo-film-dev.kakkun61.com/preview.png" />
    <meta name="twitter:player" content="https://photo-film-dev.kakkun61.com/index.html" />
    <meta name="twitter:player:width" content="400" />
    <meta name="twitter:player:height" content="650" />

埋め込むコンテンツにはルールがあるので注意しよう。

developer.twitter.com

Elm でマテリアルデザインなウェブアプリを作った

アプリ概要

アプリとしてはニッチな用途で、写真用のモノクロネガフィルムを自家現像するときに便利なタイマーアプリです。下記は埋め込まれた実際に動くアプリです。

Photo Film Dev で公開しています。

時間配分をレシピと呼んでいますが、レシピはサーバーに保存されるためログインすれば複数の端末でアプリを使用できます。

今はレシピは自分のアカウントでしか見れないですが別のアカウントに共有する機能が作れたらなと考えています。

技術構成

ソースコードGitHub に GPL3 で提供しています。

github.com

アプリの技術構成としては次の通りです。

Elm

Elm は言語としては Haskell 様の文法を採用した alternative HTML/CSS/JS です。The Elm Architecture がいい感じです。チュートリアルを読めば書けるようになります。

モジュール分割

最初は1ファイルに全部書いて始めたのですがわりとすぐにどの辺りに何を書いたか分からなくなったのと同じ名前を付けたいものが出てきたのでモジュール分割をしました。詳細は後述します。

モジュール 役割
Model Msg 以外のデータ型
データ同士の変換関数など
Model.Foo enum として定義する Foo
getset など関連する関数
Msg Msg のデータ型
Msg.Foo 入れ子になる Msg のデータ型
update 関数と対応する
Port port 関数とその引数になる型
その型と内部で使用する型との変換関数
Cmd Port で定義した関数を組み合わせた Cmd を生成する関数
Sub Port で定義した関数を組み合わせた Sub を生成する関数
Update 直和型として定義した Msg 型のそれぞれの値構築子に対応する update 関数
View 画面要素ごとに分割した view 関数
Text 多言語テキスト

Enum

Elm にはアドホック多相が組み込み関数にしかありません。例えば Step という段階を表す型を作って Haskell であれば Enum 型クラスにするところですができないのでモジュールを分けて toIntfromIntcompare などを定義することになります。標準ライブラリーの型でも Maybe など Monad になるものはモジュールごとに andThen を定義しているので正当な方法だと思います。

module Model.Step exposing (..)

type Step
    = Soak
    | Dev
    | Stop
    | …

toInt : Step -> Int
toInt step =
    case step of
        Soak -> 0
        Dev -> 1
        Stop -> 2
        …

fromInt : Int -> Maybe Step
fromInt value =
    case value of
        0 -> Just Soak
        1 -> Just Dev
        2 -> Just Stop
        …
        _ -> Nothing

compare : Step -> Step -> Order
compare step0 step1 = Basics.compare (toInt step0) (toInt step1)

Dict 様レコードでの DRY

フィールドの型が全部同じで辞書(マップ)として使用するような下記のようなレコードの場合フィールドごとに同じような処理を書こうとしてもそのままでは実装を複製するしかありません。Dict にする手もありますが存在することが分かっているのに不必要な Maybe だらけになってしまいます。

type alias TimeSpans =
    { soak : TimeSpan
    , dev : TimeSpan
    , stop : TimeSpan
    , fix : TimeSpan
    , rinse : TimeSpan
    , wet : TimeSpan
    }

そこで先の enum として作った型をキーとして getset 関数を作ります。

module Model.Step exposing (..)

…

get : Step -> { soak : a, dev : a, stop : a, fix : a, rinse : a, wet : a } -> a
get step =
    case step of
        Soak -> .soak
        Dev -> .dev
        Stop -> .stop
        …

set : Step -> a -> { soak : a, dev : a, stop : a, fix : a, rinse : a, wet : a } -> { soak : a, dev : a, stop : a, fix : a, rinse : a, wet : a }
set step value record =
    case step of
        Soak -> { record | soak = value }
        Dev -> { record | dev = value }
        Stop -> { record | stop = value }
        …

これで下記のようにアクセスできます。

timeSpans |> get step
timeSpans |> set step value

これで don't repeat yourself にのっとって処理をまとめることができます。

Msgupdate の分割

Msgupdate の分割のしかたは対応させています。

-- Msg.elm
type Msg
    = Drawer Visible
    | SelectRecipe Recipe
    | GoRun
    …

-- Main.elm
update : Msg -> Model Msg -> ( Model Msg, Cmd Msg )
update msg model =
    case msg of
        Msg.Drawer visible -> Update.drawer visible model
        Msg.SelectRecipe recipe -> Update.selectRecipe recipe model
        Msg.GoRun -> Update.goRun model
        …

Msg.Drawer には Update.drawer というように対応させています。入れ子になったメッセージである VisibleMsg.Visible モジュールに定義してあります。Model の状態による条件分岐は各々の update の中で行っています。

PortCmdSub

Model モジュールに Recipe は下記のような型で定義しているのですがこれは port 関数で JavaScript に渡したり JavaScript から渡されたりすることができません。なので Port モジュールに同名の Recipe を作り変換関数も Port に定義しました。

-- Model.elm
type alias Recipe =
    { id : UUID
    , name : String
    , timeInputs : TimeInputs
    }

-- Port.elm
type alias Recipe =
    { id : String
    , name : String
    , timeInputs : Model.TimeInputs
    }

Port.Recipe から Model.Recipe への変換には elm-form-decoder を使いました。フォームからの変換以外でも外界からデータを取り込む場合に便利です。

Port.setRecipeCmdPort.Recipe に依存していますが Cmd.setRecipeModel.Recipe に依存するように Port の変換関数を使って変換しています。Sub.changeRecipe も同じく Port.changeRecipeSub から変換していますが、msg の構築子が2つあるのは変換の成功時・失敗時を表現するためです。

-- Port.elm
port setRecipeCmd : Recipe -> Cmd msg -- ここの Recipe は Port.Recipe

-- Cmd.elm
setRecipe : Recipe -> Cmd msg -- ここの Recipe は Model.Recipe
-- Port.elm
port changeRecipesSub : (Array Recipe -> msg) -> Sub msg -- ここの Recipe は Port.Recipe

-- Sub.elm
changeRecipes : (List RecipeDecoderError -> msg) -> (Dict String Recipe -> msg) -> Sub msg -- ここの Recipe は Model.Recipe

Material Design

Material Design を採用しライブラリーとしては elm-mdc を使用しました。UI センスがなくても Material Designガイドラインにしたがえばそれっぽくなるので便利です。

github.com

Elm のライブラリーは Elm しか内包できません。このライブラリーは Elm の他に JavaScript なども含むため Git リポジトリーとして公開され、サブモジュールとして依存します。

elm.jsonpackage.json は下記のようになります。

// elm.json
{
    "type": "application",
    "source-directories": [
        "src",
        "elm-mdc/src"
    ],
    "elm-version": "0.19.1",
    "dependencies": {},
    …
}
// package.json
{
  "dependencies": {
    "elm-mdc": "file:elm-mdc",
    …
  },
  …
}

ビルドタスクは GNU Make で管理しているのですが Parcel を使用しているにもかかわらず上記事情のため elm-mdc の面倒も見ているので少々複雑になっています。

多言語テキスト

Text モジュールでは下記の型の値を列挙しています。といっても今のところ日本語のみですが。

type alias Text = Language -> String

モジュール依存関係

モジュールの依存関係を下図に示します。Text は図から省略しています。

f:id:kakkun61:20200720040856p:plain
モジュール依存関係

Text は他のいずれにも依存しません。Model グループは Text にのみ依存します。黄色で ModelMsg を囲っているものは単に矢印の数を削減するためのもので、黄色枠への矢印もしくは黄色枠からの矢印はその中の1個以上への矢印もしくはその中の1個以上からの矢印と読んでください。MsgModelText にのみ依存します。他に特記すべきは PortCmdSub のみから依存され必ずラップされたものが他から参照されます。

Firebase

サーバーは自前で書くつもりだったのですが Firebase をさわってみたら認証とストレージだけで十分だったのでサーバーは自前では立てないことになりました。local storage を使ってスタンドアローンで動くところまで作っていましたが Firestore がオフラインの面倒も見てくれるので local storage の実装は消しました。

先に書いたように Elm のライブラリーは Elm のみに限定されるので、必然的に JavaScript に依存する Firebase のラッパーライブラリーはありません。とはいえ書くのはグルーコードなので大した問題ではありません。

クレジットページ

頒布をともなう場合、多くのオープンソースライセンスはコピーライト表記とライセンス表記を求めます。なのでクレジットページを作成しました。これは自動生成されるようにしています。NPM License CheckerElm License Checker を使用して生成しています。Elm License Checker はなかったので作りました。(Elm License Checker は PureScript 製です。作成時 Elm で CLI を作れることを知らなかったので。)

www.npmjs.com

謝辞

Discord の Elm-jp サーバーのみなさまにはお世話になりました。

Windows Terminal に MSYS2/MinGW64 を追加する

Windows 10 2004 にしたので Windows Terminal をさわってみた。自分の用途だと Cmder はもう使わなくていいなと思う。

さて、Windows Terminal に MSYS2/MinGW64 を追加したのでその設定を示す。

{
    …,

    "profiles":
    {
        …,

        "list": [
            …,

            {
                "guid": "{43257a9a-8d31-4208-8ed4-3d4365d44bd1}",
                "name": "MSYS2/MinGW64",
                "commandline": "%ChocolateyToolsLocation%\\msys64\\msys2_shell.cmd -defterm -no-start -mingw64 --login -i",
                "icon": "%ChocolateyToolsLocation%\\msys64\\mingw64.png"
            }
        ]
    }
}

profiles.list にレコードを追加する。

GUID は適当に生成した GUID を設定する。

commandlinemsys2_shell.cmd を経由してシェルを起動するようにする。msys2_shell.cmd を使用することで初期設定や環境変数の設定などをコマンドライン引数で行える。

アイコンは mingw64.exe から抽出したものを使用している。

Windows のホームフォルダーにドットファイルを作らないでほしい

Windows ではファイル名の先頭にドットを付けるだけでは隠しファイルにならないのでユーザーのホームフォルダーを汚染することになる。

だいたいの場合では LOCALAPPDATA 環境変数の示す場所にソフトウェア名のフォルダーを作ってその下を自由に使えばいい。

LOCALAPPDATA の示す場所は普通 C:\Users\kazuki\AppData\Local といったぐあいだが、ユーザーが変えている場合もあるので固定パスにするのはよろしくない。

また、隠しファイルを作りたい場合はファイルシステムの隠し属性を有効にしてほしい。そうするとデフォルト状態ではそのファイルは見えなくなる。

LOCALAPPDATA 以外の似たようなフォルダーはWindowsのディレクトリ構成ガイドライン - torutkのブログの記事がくわしい。

torutk.hatenablog.jp