Runner in the High

技術のことをかくこころみ

Elmアプリケーションにおける型と抽象化手法

Elmは機能を絞った言語であるためScalaHaskellのような高度な抽象化は言語機能上できないが、使う使わないに関わらず知識として「やれること」「やれないこと」を知っておくと、コード表現に幅が出る。

Extensible Record

ざっくり言うとレコードのプロパティを抽象化することができる。

例えば以下のNamableレコードは、nameというプロパティを持つレコードすべてを対象にする。

{-| 特定のレコードに依存しない抽象的なnameというプロパティを持つレコードを対象とする
-}
type alias Namable a
    = { a | name : String }


getName : Namable a -> String
getName { name } =
    name

つまり、以下のようなデータ構造があるとしても、データ構造としては受け付けるものの直接的な依存はしなくなる。

type alias User =
    { name : String
    , email : String
    , age : Int
    }


-- getName関数はUserレコードに依存しないがUserレコードを受け付ける
getName : Namable a -> String
getName { name } =
    name

上記のようなインターフェースになることで、getName関数はUserレコードの構造を持つ値を受け取るとしてもUserレコードのデータ全てに関心があるわけではなく、ただ単にnameという属性のみに関心があるということがインターフェースレベルで表明できる。また、関数の実装も特定のレコードに依存しないものになるため抽象化できる。

一方で「ageやemailの値によってgetName関数の挙動を変える」などの仕様が別とある場合には、それは間違いなく仕様レベルで"getName関数はUserレコードに依存しているべきだ"ということが分かる。なので、そのようなケースではあえてExtensible Recordではなく直接的にレコード名を指定することで関数のインターフェースに依存を表明させるべきだろう。

Narrowing Types

関数が受け取るインターフェースを特定の型に依存させるというのがNarrowing Typesの考え方にあたる。

たとえば以下のようにUserというカスタム型の各バリアントの情報をHTMLにする関数があるとする。

type User
    = UserLoggedIn LoggedInInfo
    | UserSuspended SuspendedInfo
    | UserBanned BannedInfo

viewLoggedIn : User -> Html msg

viewSuspended : User -> Html msg

viewBanned : User -> Html msg

受け取るデータがUser型に限定されている点ではまずまずだが、それでもまだ上記の関数はインタフェースの設計が適切とは言えない。なぜなら、User型はカスタムタイプであるため、コード上で実際に渡る値がバリアントのうちどれであるかはインタフェースからは判断できないからである。こうなると関数をさらに読んでいかなくては実際の挙動が把握できない。

なので、正しくは上記の関数は以下のように、User型の各バリアントが持つデータに依存させるべきだ。こうすることで、さらに関数の依存を限定でき、インタフェースから関数の挙動が推測しやすくなる。

viewLoggedIn : LoggedInInfo -> Html msg 

viewSuspended : SuspendedInfo -> Html msg

viewBanned : BannedInfo -> Html msg

ではこの考え方を抽象化でどのように使うかというと、異なるデータ構造において共通となる型を用意し、抽象化したい関数はその共通の型だけに依存させる。考え方としては若干Extensible Recordに近い。

例えば、以下はMember型とAdmin型に共通なName型を用意し、関数のインタフェースをName型に依存させる例。

-- Member.elm

type Member =
    Member Name


-- Admin.elm

type Admin =
    Admin Name


-- Name.elm

type Name
    = Name String

こうすることで、データとしてName型を持っているものともっていないものを区別できる。また、NameモジュールにはMemberとAdminが必要とする「名前に関するロジック」を凝集させることができるため、Nameという軸でMemberとAdminを抽象化できる。

ここで重要なのがName型を本当に必要な型だけに与えることだ。もしAdminとMemberで名前に関する仕様が異なるのであれば、それは同じName型に依存させてはだめで、AdminとMemberでそれぞれ異なるName型を用意するべきだ。同じ型に依存しているということは、仕様が同じであるということを意味する。

これはプリミティブ型の扱いにも同じことが言える。通常、Stringというのはアプリケーションの中でもっとも頻繁に表れる型だが、Stringへの依存は文字列というほぼ制限のない仕様に対する依存を表明している。しかし、我々が作るアプリケーションにはメールアドレス、ユーザー名、ニックネーム、などの"仕様を持つ文字列"が存在しており、関数やデータ構造はその仕様に特化した型に依存することでインターフェースとして仕様を表現することが望ましい。型と仕様が見つかれば、それはモジュールとして抽象化して抽出できる。

また「ある型が特定の型をもつかどうか」というのは幽霊型というテクニックにもつながる。幽霊型を使うころで状態遷移のパターンを型で制限できたりもする。 izumisy.work

Tagger

Elmはカスタム型もレコードもすべて関数として扱うことができるため、それを利用して任意の値を持つカスタム型のバリアントを受け付けることができる。ライブラリなどを作っていると使う場面がでてくるが、普通のアプリケーション開発ではあまり使う場面があるとは言えない。

type Msg
    = UpdateEmail Email
    | UpdateName Name
    | NoOp


{-| この関数はUpdateEmailのバリアントだけを引数で受け付けることができる
-}
emailUpdating : (Email -> a) -> Cmd msg

つまりタグを持つバリアントというのは、タグを受け付ける関数とイコールである。このような関数をElmにおいてはTagger(タグする関数)と呼ぶ。

あるのか分からないが、たとえば使いどころとして特定のMsgバリアントだけを受け取ってupdateを実行するような関数を実装したいときなどが挙げられる。その場合にはTaggerを使えば、上のemailUpdatingの例のようにUpdateEmailだけを受ける関数が作れる。

抽象化の観点では言えば、上のemailUpdating関数はUpdateEmailに限らずEmail型をタグとして持つものであればなんでも受け入れることができる。したがって、タグの型は限定するが、タグの型が同じでさえあれば値はなんでもよい。

移譲、DIP

DIPに関する記事はここに書いていた。 izumisy.work

また、英語になるがDEVにもモジュールの実装を移譲モジュールで抽象化する件の記事も書いていた。 izumisy.work

所感

書いてて思ったけどこれ全部Elmというよりは静的型付言語に共通する手法という感じがする。

でもElm書いてると細かい言語文法とかライブラリの使い方みたいな場当たり的なものじゃなくて、型を中心にした設計方針みたいなのを考えるほうに気が向くからいいですね。

メルカリでiMac 21inchを売るときのあれこれ(発送とか初期化とか)

先日2019年モデルのiMac 21inchをメルカリで売った。

下取りとか買取で調べると6-7万くらいで買い叩かれる&一度発送しての査定なのでかなり不安(送ったら1000円とかにされたりするかもしれないし)だが、メルカリであれば先に値段が決まるので嬉しい。

手数料は高いが仕方ない。

初期化について

オークション等で譲渡する場合にはiMac本体の初期化は必須。

手順に関しては公式が用意しているものがあるので、これを参照するのがよい。

support.apple.com

ひとつ注意点があり、以下のようなiMacのスペックのダイアログを画像として商品説明に載せる場合には、かならず初期化の前に写真として撮っておく必要あり。

f:id:IzumiSy:20200821102752j:plain
iMacのスペックのダイアログ

一度初期化してしまうと、再セットアップせずにはスペックを見ることができないので、またセットアップからの初期化が必要になってしまう。

シリアルナンバーに関してはそれ用いて保証期間をAppleの公式サイトでチェックしたりする用途で使うようだが、公開してしまうと偽造などに利用されてしまうとかなんとかで念のため隠している。

本当に隠す意味があるのかは不明。

保証について

コメント欄で「この商品は保証書はありますか?」とか聞かれることがあるが、それに関しては「ないが、シリアルナンバーから保証期間を確認できる。この製本の保証期間は〇〇までです」と伝えておけばよい。

www.apple.com

Appleの公式ストアで買うと確実についてこないが、家電量販店などで購入した場合にはその量販店用の保証書がついてくることはあると思う。

発送について

Apple製品というのは箱にも価値があるとか言われるので、人によっては発送するに当たってiMacの箱を包むダンボールをさらに用意したりする。あるいは、iMacを買うと純正の化粧箱を覆うダンボールがさらについてくるが、自分はそれは処分してしまっていた。

だが、自分の場合は追加でダンボール買うのもだるい(あと値段も上げて交渉合戦になりたくない)ので、説明欄に「この商品は純正の箱そのままで送ります」としっかり書いて出品することにした。

この記事を書いた時点でiMac 21inch 2019年モデルはヤマトの140サイズ扱いになる。自分はらくらくメルカリ便で送った。

www.mercari.com

Ubuntu 20.04でUSBのWiFiアダプタ(WDC-300SU2SBK)を認識させる方法

インターネットで調べたところ、どうやらUbuntuがサポートするrtl8192cuというチップとして認識させれば動くとのこと。

というわけで、rtl8192cuのモジュールをmodprobeでロードし、lsusbで分かったベンダIDとプロダクトIDをカーネルに認識させる。

$ sudo modprobe rtl8192cu
$ echo "056E 4009" | sudo tee /sys/bus/usb/drivers/rtl8192cu/new_id

こんな感じで、対象となるモジュール名の配下に置かれている new_id ファイルへデバイスの情報を書き込むとドライバの対象として未登録の機器であっても認識させることができる。つまり、実質的にどんな野良WiFiアダプタであっても中のチップセットの種類さえサポートされているものであれば、この方法で動かすことができる。

あとはudevを使ったり/etc/rc.localに上のコマンドを書いておくなりして、起動時にWiFiアダプタ用のモジュールをロードし自動的にネットワークへ繋がるようにすればよい。

askubuntu.com

BigQueryのDATE関数とタイムゾーン周りの挙動

よく分からんところがあるので整理した。

SELECT 
  date("2020-7-1T23:59:59+0900", "Asia/Tokyo") as tzDateWithTzString, 
  date("2020-7-1T23:59:59", "Asia/Tokyo") as dateWithTzString,
  date("2020-7-1T23:59:59+0900") as tzDate,
  date("2020-7-1T23:59:59") as date_

上記のSQLを実行すると以下の結果になる

tzDateWithTzString dateWithTzString tzDate date_
2020-07-01 2020-07-02 2020-07-01 2020-07-01

注目すべきはdateWithTzStringで、ISO8601形式における時差指定がない場合にはdate関数に指定されたタイムゾーン分の時差が時間に適用されている。

タイムスタンプの時差とタイムゾーン文字列の両方が指定されている場合の挙動

上の結果におけるtzDateはタイムスタンプ側でも+9時間の時差が指定されているが、同時に Asia/Tokyo というタイムゾーンも指定されている。

結果だけ見るとdateWithTzStringとは違いタイムゾーンの指定が無視され時差の適用が行われていないように見えるが、タイムスタンプにおける時差指定とタイムゾーンの時差が異なる場合を見てみると実際には適用が行われているらしいことが分かる。

以下はその例となるクエリ。

select 
  date("2020-7-1T23:59:59+0900", "Asia/Vladivostok") as date1,
  date("2020-7-1T23:59:59+0900", "Asia/Ulaanbaatar") as date2,
  date("2020-7-1T23:59:59+0900", "Asia/Tokyo") as date3

結果は以下になるが、タイムスタンプ側で時差指定がすべて+9時間であるにも関わらずタイムゾーンによって結果となる日付が変動していることが分かる。

date1 date2 date3
2020-07-02 2020-07-01 2020-07-01

この挙動を見るに、おそらく時差指定を含んだタイムスタンプ文字列とタイムゾーンの両方が指定されている場合の内部的な挙動はまずタイムスタンプ時間をUTCへ変換し、その上で改めてタイムゾーンの時差を適用するという動きになっているのではないかということが推測できる。

ウランバートルは時差が+8時間であるためタイムスタンプをUTC変換した上で時差適用しても 22:59:59 にしかならない。一方で Asia/Vladivostok は時差が+10時間なため、タイムスタンプがUTC変換された上で時差が適用されると翌日の 00:59:59 になる。

Interpreter Pattern in Elm: 副作用をテスタブルにする

Elmは純粋関数型プログラミング言語なので基本的にはアプリケーションを構成するすべての関数はテスタブルであるが、唯一Cmd型だけはテストすることができない。

たとえば以下のようなupdate関数においてPersistToStorageのメッセージが渡された際に必ずストレージへの保存を実行するCmdが発行されているかどうかをテストしたいとする。

update : Msg -> Model -> ( Model, Cmd msg )
update msg model =
    case msg of
         PersistToStorage value ->
               ( Saving, persistToStorage value )

         -- ...


-- ports


port persistToStorage : Encode.Value -> Cmd msg

ElmアプリケーションにおいてCmdは一度ラップされてしまうと、中のmsgを取り出すことはできない。またカスタム型であるためelm-testにおいて等価チェックのアサーションも使うことができない。以上の理由からCmdはテスタブルな型ではないということが分かる。

もちろん、CmdをテストせずModelの状態のみをチェックすることで実質副作用が発行されていることと同義とすることも可能である。Elmアプリケーションにおいて副作用というのはアプリケーションの状態に直接的に影響を与えるものではないし、そもそも「副作用が発行されているかどうかをテストするべきなのか?」という疑問は常に持っておくべきだと言える。

しかし、どうしても副作用をテストしたいという場合にはInterpreter Patternを用いることで副作用をテスタブルにすることができる。

Interpreter Patternとは

Interpreter Patternはざっくりいうと「アプリケーションの処理と実装を分離する」ためのパターンにあたる。ScalaHaskellなどの関数型プログラミング言語の文脈においては有名なパターンで、計算機の実装などがよくある例である。

www.youtube.com

より具体的な実装方法で言うと「アプリケーションがどう動くか」を型など純粋なもので表現し、その型を解釈(Interpret)する別の関数に副作用の発行を移譲することで副作用のある部分と純粋な部分を切り離すという形になる。

ElmにおけるInterpreter Pattern

さて、ElmにおいてInterpreter Patternはどのような実装になるかというと、ただ単にupdate関数の副作用を自前で定義したカスタム型で置き換えるだけである。こうすることでupdate関数は完全にテスタブルな型のみを返すようになる。

update : Msg -> Model ( Model, ExCmd )
update msg model =
    case msg of
        PersistToStorage value ->
              ( Saving, ExPersistToStorage value )

        -- ...


type ExCmd
      = ExPersistToStorage Encode.Value

そして、ExCmdを解釈してCmd型の発行を行うInterpreterとなるtoCmd関数を用意し、main関数の呼び出し時にupdate関数と接続する

toCmd : ExCmd -> Cmd msg
toCmd exCmd =
    case exCmd of
         ExPersistToStorage value ->
               persistToStorage value


main : Program () Model Msg
main =
    Browser.element
        { init = init
        , view = view
        , update = (\msg model -> Tuple.mapSecond toCmd <| update msg model) 
        , subscriptions = \_ -> Sub.none
        }

これでCmd型に依存するのはtoCmd関数だけになったため、もともとの目的であった「update関数はPersistToStorageのメッセージでストレージへの保存を実行するCmdを発行しているか」を以下のようにテストすることができる。

suite : Test
suite =
    test "update" <|
        \_ ->
            init
                |> update (PersistToStorage "this is an apple")
                |> Tuple.second
                |> (\exCmd ->
                    case exCmd of
                        ExPersistToStorage _ ->
                            True

                        _ ->
                            False
                )
                |> Expect.equal True

これでupdate関数を完全にテスタブルなものにすることができた。

余談

Elmにおいてはアプリケーションでランタイムエラーが起きることはないため、どちらかといえばテスタビリティを上げるという観点でInterpreter Patternのような設計を行うことが多い。

見方を変えれば、Elmアプリケーションの実行を司るElmカーネルそのものがJavaScriptとの連携を行うInterpreterとしての役割を果たしているため、他の言語におけるInterpreter Patternのように「実行時エラーが起きる可能性があるレイヤとそうでないレイヤを分離する」というような実行時安全性を維持するための分離はElmにおいて全くもって必要ない。

もちろんJavaScriptやTypeScriptでは必要ではあるが。

elm-multi-waitableで非同期処理の完了待ちをModelに表現する

Elmアプリケーションにおいて非同期処理の完了待ち実装をいい感じにするelm-multi-waitableというモジュールを公開した

github.com

内部的には状態遷移のタイミングでデータを受け取って保持するステートマシンのようなものだが、これを使うことでフロントエンド・アプリケーションによくある「非同期ないし直列に複数データの取得を行い完了待ちをする」というような処理の実装を改善できる。

非同期処理をModelで表現する難しさ

例えば、以下のようにLocalStorageへのアクセスと複数のWebAPIへのリクエストの完了待ちが必要になるケースがあるとする。

また、すべてのリクエストが完了するまではローディング中画面を出し、すべてが完了したらローディング完了画面を出すという仕様がある。

f:id:IzumiSy:20200711234003j:plain

この仕様のModelを最も簡単に表現するのであれば、データの取得が完了している/完了していないをMaybeで素朴に表現することになる。

すべてがJustになっていればview関数でローディング完了画面を出すといった具合になる。

type alias Model =
    { auth : Maybe Auth
    , user : Maybe User
    , option : Maybe Option
    }

これはこれで一旦動くものは作れるが、以下の問題点を含んでいる。

  • データを扱うのに毎回Maybeを剥がす処理が必要になりコードが冗長になる
  • 直列にすべてのデータを取得する実装ではデータの取得順が変わる度に完了チェックの場所が変わる(UserとOptionの取得順を入れ替えるような場合には都度完了チェックのタイミングも変える必要がある)
  • 並列にすべてのデータを取得する実装では個別のデータ取得完了毎に完了チェックの処理が必要になる(どの順番でデータの取得が完了するか分からない&完了チェック自体が抜け漏れる可能性がある)

そもそも、値をMaybe型で包んでいるのはローディング画面を出したいという仕様のためだけであって、ローディング画面が終了したら存在しているものはMaybeである必要がない。可能であればMaybeではない型であってほしい。できれば、以下のようなデータ構造であってほしい。

type Model 
    = Loading -- ローディング中
    | Loaded Auth User Option -- ローディング完了

しかし、このコードは理想論であって実際には実現できない。なぜならローディング中には非同期的にLocalStorageやWebAPIからのデータが取得されていくため、取得過程のデータも保持する必要があるからだ。

ここでelm-multi-waitableを使うともう少しだけ近い形で実現することができる。具体的には、以下のようなModelの設計にすることができる。

type Model
    = Loading (MultiWaitable.Wait3 Msg Auth User Option)
    | Loaded Auth User Option

elm-multi-waitableの使い方

まずinit関数でLoadingの初期化を行う

init : Model
init =
    Loading <| MultiWaitable.init3 Done

MultiWaitable.initN関数は待ち合わせているすべての非同期処理が完了した際に発行されるMsgを第一引数に取る。

第一引数で指定されているDoneは完了待ち対象となるデータの型(今回の例ではAuth, User, Option)をタグに持つもので、Msg型は以下のような設計になる。

type Msg
    = AuthFetched Auth
    | UserFetched User
    | OptionFetched Option
    | Done Auth User Option

上記のMsgをハンドリングするupdate関数は以下。

update : Msg -> Model -> ( Model, Cmd msg )
update msg model =
    case (model, msg) of
        ( Loading waitable, AuthFetched auth ) ->
            waitable
                |> MultiWaitable.wait3Update1 auth
                |> Tuple.mapFirst Loading

        ( Loading waitable, UserFetched user ) ->
            waitable
                |> MultiWaitable.wait3Update2 user
                |> Tuple.mapFirst Loading

        ( Loading waitable, OptionFetched option ) ->
            waitable
                |> MultiWaitable.wait3Update3 option
                |> Tuple.mapFirst Loading

        ( Loading _, Done auth uesr option ) ->
            ( Loaded auth user option, Cmd.none )

MultiWaitableはupdateのための関数としてwaitNUpdateN関数群を提供しており、たとえばそのうちwait3Update1関数は以下のようなシグニチャになっている。

wait3Update1 : a -> Wait3 msg a b c -> ( Wait3 msg a b c, Cmd msg )

この関数はaを適用して更新されたWait3型の値と、完了時であればinit3関数で与えられた完了Msgの発行コマンドをタプルで返している。

MultiWaitableモジュールを使えば、update関数においてはWaitN型の更新だけをすればよく、待ち合わせ中のデータがすべて揃ったかどうかを開発者自身が都度気にする必要はない。また、機能追加による改修の際にも、Modelの設計からローディング/ローディング完了という状態が存在しているとひと目で分かることや、init関数の初期化部分で完了時のMsgが把握できることなどから可読性も高くなる。

Elmにおいて複数の非同期処理の完了待ちはModelの設計が比較的難しい部分であるが、elm-multi-waitableを使うことでModelのデータ構造をシンプルかつ分かりやすく表現させることができる。

関連パッケージ

elm-multi-waitableは複数Taskの完了待ちを支援するelm-task-parallelというパッケージから大いに影響を受けている。

izumisy.work

また、非同期処理の完了待ちに関してはリチャード・フェルドマンもelm Europe 2018の"Make Data Structures"で同様の話をしている。

f:id:IzumiSy:20200712105918j:plain "Make Data Structures" by Richard Feldman

彼のトークスライドはIndexeDBとWebAPIの両方からデータの取得を行うケースでのModel設計ではあるが、Maybeという「ある/なし」しか表現できない文脈の少ない型をできるだけ減らしていこう、という方向性では同じく参考になるだろう。

vim-lspでElm用のVim環境を構築する

ElmCast/elm-vimをやめてvim-lspとasyncompleteを使ってelm-language-serverに対応したVim環境を整えたのでメモ。

github.com

プラグイン導入

vim-plugでvim-lspとasyncomplete系のプラグインを一気に導入する。すでに導入していれば不要。

Plug 'prabirshrestha/async.vim'
Plug 'prabirshrestha/asyncomplete.vim'
Plug 'prabirshrestha/asyncomplete-lsp.vim'
Plug 'prabirshrestha/vim-lsp'
Plug 'https://github.com/IzumiSy/vim-lsp-settings'

最後のvim-lsp-settingsは正しくはmattnさん作のプラグインで、vim-lsp用の設定を自動で適用してくれかつ各言語用のサーバーがなければインストールを実行してくれる超便利なプラグイン。これは絶対あったほうがいい。

しかし、この記事を書いた時点ではelm-language-server用の設定にバグがあり正しくサーバーが起動されなかったため、一旦自分で修正してフォークしたものを使っている。

PRは出しているので、VimでElmの環境を整えたい人は以下のPRがマージされるまでは自分のやつを使ってもらうか、自分でvim-lsp用のelm-language-serverの設定を手で書くのがいいだろう。

github.com

なお、Vimは8.2 patch 929からビルトインでElmのシンタクスハイライト設定を内蔵しているため、もはやElmをVimで書くのにシンタクス用プラグインも導入しなくてよい。

github.com

設定

自分は基本的に各プロジェクトのdevDependenciesにElm関連のバイナリをインストールするのでelm-language-serverの設定としてバイナリのパス設定を追加する。

let g:lsp_settings = {
\   'elm-language-server': {
\     'initialization_options': {
\       'elmPath': './node_modules/.bin/elm',
\       'elmFormatPath': './node_modules/.bin/elm-format',
\       'elmTestPath': './node_modules/.bin/elm-test'
\     }
\   }
\ }

ファイル保存時のフォーマッター実行も追加。

au BufWritePre *.elm :LspDocumentFormat

あとカーソルがある部分の関数やレコード定義を左右分割か別タブで表示する設定も入れている。

nnoremap <leader>df :vert LspDefinition<CR>
nnoremap <leader>tt :tab LspDefinition<CR>

以上、これだけでVimでも補完を効かせてElmが書ける。定義元へも飛べる。

vim-lspの設定はかなり面倒だと思い込んでいたが、実際にはvim-lsp-settingsを入れておけば勝手に言語サーバーのインストールから設定までやってくれるので、ほとんどIDEと体験は変わらなかった。

あとはElmファイルをVimで開くと「elm-language-serverをインストールするか」というような旨のメッセージが出るので、それに従ってインストールさせればよい。elm-language-serverがグローバルインストールされている場合にはそちらが優先されるのかも知れないが、自分は試していない。

2020年6月のクラウドバンク

izumisy.work

今年もふと思い出したので書いてみる。以下が先月時点での収益レポート。

f:id:IzumiSy:20200705002604p:plain
2020年6月の収益レポート

ちょうど新型コロナウイルスが盛り上がってきたのが多分3-4月頃で、その直後しばらく新規の投資案件が出てこなくて非常に心配になった。だが今は新しくスター・マイカとの共同ファンドを出したりするなど、アメリカンファンディングのようにサービス自体が完全停止するということはなさそうである。

投資額も増えてきたからか、税引後収益も1万超えに突入してきた。コロナウイルス真っ只中で6%超えの利回りが出る原理はよくわからないが、おそらく太陽光系案件がコロナウイルスとは関係なく安定して高利回りを生んでいるのではないかと推測している。CEO金田さんもクラウドバンクのセミナーにてFITの権利を入手するルートをクラウドバンクは持っており引き続き高利回りの太陽光案件を出していくと言っていたので、若干期待が持てる。

東京でまたコロナウイルス感染者が増え始めている現状の影響がどの程度出てくるのかわからないが、余剰資金がある限りは少しづつ突っ込んでいきたい。