📰 Topics

ViteとTypeScript4.0のVariadic tuples、そしてRustの開発環境整備など。
あと、見出しの構成を変えました。

Table of Contents

見たこと/読んだこと

【翻訳】技術的負債という概念の生みの親 Ward Cunningham 自身による説明

t-wadaさんの翻訳記事+所感。

以下は新しい発見でした。

  • Ward Cunningham氏は技術的負債とは言ってない. 負債である.
  • 負債はドメイン知識に関するもの
    • 雑な設計を負債とは呼んでいない
  • 最初から設計/実装をしっかりやることはむしろMust
    • そうしないと負債を返済できなくなる

完全同意です☺️

ドラゴン桜2 10巻

ドラゴン桜は1からすべて読んでいます。
教える側、学習する側のそれぞれについて新しい気づきを与えてくれます。

最近発売された10巻を読みました。


習慣化を促すには『仕組みを利用すること』が大切というフレーズが心に残りました。
具体的なソリューションとしてはみんチャレが紹介されていました。

『人は同じ属性/目的を持つ人と行動した方が習慣化しやすい』そうです。

試したこと

Vite

ViteはVue.jsの作者Evan Youさんがメインで開発しているビルドツールです。
1.0.0-betaのフェーズに差し掛かっていたので試してみました。

READMEに書かれている通りに実行したら、開発サーバが立ち上がりました。

npm init vite-app vite-use
cd vite-use
npm i
npm run dev

package.jsonの依存関係のシンプル。

  "dependencies": {
    "vue": "^3.0.0-beta.15"
  },
  "devDependencies": {
    "vite": "^1.0.0-beta.3",
    "@vue/compiler-sfc": "^3.0.0-beta.15"
  }

TypeScriptとSassを入れてみました。

npm i -D typescript sass

特別なことをしなくてもそのまま動きます。すごい!
試しに作ったサンプルです。

 src
├──  App.vue
├──  assets
│  └──  logo.png
├──  components
│  └──  HelloWorld.vue
├──  index.css
├──  main.js
└──  util.ts
App.vue
<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <HelloWorld title="サンプルコード"/>
</template>

<script lang="ts">
import HelloWorld from './components/HelloWorld.vue'
import { defineComponent } from 'vue'

export default defineComponent({
  components: {
    HelloWorld
  }
})
</script>
util.ts
export const plusOne = (x: number): number => x + 1
components/HelloWorld.vue
<template>
  <h1>{{ title }}</h1>
  <p>Count: {{ count }}</p>
  <button @click="increment">カウントを増やす</button>
  <p><code>components/HelloWorld.vue</code> を変更するとホットリロードされます</p>
</template>

<script lang="ts">
import { plusOne } from "../util"
import { defineComponent, reactive, computed } from 'vue'

export default defineComponent({
  props: {
    title: { type: String, required: true }
  },
  setup() {
    const state = reactive({
      _count: 0,
    })

    function increment() {
      state._count++
    }

    return {
      count: computed(() => plusOne(state._count)),
      increment,
    }
  }
})
</script>

<style lang="scss">
p {
  code {
    color: red;
  }
}
</style>

所感

TypeScriptやSassなどの拡張も含め、npm installするだけで使えのが素晴らしいですね。
この辺のメンテナンスは面倒なので、そこをViteが担保してくれるのはアツイです..。

esbuildの速さはサンプルプロジェクトレベルだと実感できませんでした。
tscより20~30倍速いと言われているので期待してます😊

【TypeScript】Variadic tuples

TypeScript4.0の目玉と呼ばれている機能が紹介されています。
まだ仕様が変わるかもしれないので要注意とのこと。

試してみました。

npm i -D typescript@next

主な特徴です。

  • 末尾だけでなく先頭や途中を可変にできる
  • 複数回登場させられる

シンプルな使い方の一例。

type Foo<T extends unknown[]> = [string, ...T, number];
type T1 = Foo<[number, number]>;  // [string, number, number, number]

ここでは、元記事で途中にあるInferCallbackResults<T>のみ詳しく説明します。

type InferCallbackResults<T> = 
  T extends (... t: [...infer Arg, (res: infer Res) => any]) => any ? 
    Res : never

InferCallbackResults<T>は、最後の引数がcallback関数である関数の型をTにとりcallback関数の引数型を返却する型です。
文字にすると意味が分からないと思うので、具体例を出します。

// 最後の引数として指定されたcallbackの引数型を返却
type InferCallbackResults<T> = 
  T extends (... t: [...infer Arg, (res: infer Res) => any]) => any ? 
    Res : never

// InferCallbackResults<T>のTに指定する関数型
// 引数2つ + 最後にcallback関数
type ExampleFunc = (a: number, b: number, func: (answer: string) => number) => void;

// 期待値はstring. (answer: string) => number の answerに該当する型だから
declare const r: InferCallbackResults<ExampleFunc>;

// 型を確かめるための関数
function checkType<T>(a: T) {}

// rがstring型であることを確認する
checkType<string>(r)
// -> OK

InferCallbackResults<T>がなぜ最後の引数で指定されたcallback関数の引数型を返却するのかを見ていきましょう。

Type inference in conditional types

この辺で以前説明しています。

inferはキャプチャしたい型の直前につけます。
なので、実質意味合いは以下のようになります。(コードとしては不正)

type InferCallbackResults<T> = 
  T が (... t: [...Arg, (res: Res) => any]) => any に割り当て可能なら Res そうでなければ never

汎用関数型

全ての関数型は(...arg: any[]) => anyと表現できます。
これを先ほどの(... t: [...Arg, (res: Res) => any]) => anyに割り当てると関係は以下になります。

No 汎用表現 今回の表現
❶ (...arg (...t
❷ : any[] : [...Arg, (res: Res) => any]
❸ )=> any )=> any

実質の違いは❷だけです。
❷は引数表現であり、最後が(res: Res) => anyで終わるという制約がついています。

type InferCallbackResults<T> = 
  T が 引数の最後に (res: Res) => any 型の関数を持つなら Res そうでなければ never

よってInferCallbackResults<T>は最後の引数で指定されたcallback関数の引数型を返却することが分かります。

【Rust】疑問符演算子

以下のようなことを社内Rust勉強会で学びました。

?がunwrap()とreturn Err(Err)を兼ね備えているのは本当に凄い! 感動しました😍

let hoge = load_hoge()?;
return do_anything(hoge)

同じことをowleliaを使ってTypeScriptで書くと以下のいずれかになります。

Goライクな書き方

const hogeOrErr = await loadHoge();
if (hogeOrErr.isLeft()) {
  return left(hogeOrErr.error);
}

return right(doAnything(hogeOrErr.value));

関数型な書き方

const hogeOrErr = await loadHoge();
return hogeOrErr.mapRight(doAnything)

【Python】Loguru

Pythonのロギングライブラリ、Loguruを試してみました。

標準モジュールより設定が簡単で気軽に使えます。

調べたこと

WindowsのMakeをcmdで実行させる方法

Windowsでsh.exeにパスが通っていると、GNU Makeはそちらを優先するようです。

これではcmdを前提に作成したMakefileが、実行者の環境によって動かなくなってしまいます。
MakefileでSHELLにcmdを指定することで解決しました。

SHELL=cmd

【TypeScript】Cannot find module ‘tslib’

Owleliaの動作確認しようと思ったら見慣れぬエラーが発生しました。

単にtslibが依存関係に含まれていなかっただけです。
大抵の場合、フレームワークなどの依存関係にtslibが含まれているため、今まで問題にならなかっただけでした..。

Owleliaの依存関係にtslibを追加して解決しました。

この問題は--importHelpersオプションが有効の場合のみ発生します。
有効にしていなければtslibは使用されず、トランスパイルされたjsファイルに直接埋め込まれるため。

【Jest】実行が遅い場合の対処法

--maxWorkers=1を指定すると速くなります。
デフォルト値は3ですが、Workerの準備に時間がかかって遅くなっているとか..🤔

https://github.com/kulshekhar/ts-jest/issues/259

https://github.com/kulshekhar/ts-jest/issues/259#issuecomment-504088010

https://github.com/facebook/jest/issues/8202

整備したこと

【Rust】IntelliJ IDEAでの開発環境整備

プロジェクト作成

  • Windows
  • rust-upがインストール済み

cargoでプロジェクトを作成して実行します。

$ cargo new cargo-use
$ cd cargo-use
$ cargo run
   Compiling cargo-use v0.1.0 (C:\Users\syoum\work\sandbox\rust\cargo-use)
    Finished dev [unoptimized + debuginfo] target(s) in 1.38s
     Running `target\debug\cargo-use.exe`
Hello, world!

IDEAで動かす

プラグインとしてIntelliJ Rustが必要です。

作成したプロジェクトを読み込んでから、main.rsで実行します。

以前cargoを使わずにRustコードを読み込んだ時は、補完が効きませんでしたがCargoで作成したプロジェクトでは問題なく動きました。
型推論やQuick Fixも問題なかったです。

せっかくなのでCargo.tomlをCargo2.tomlとしてコピーするコードを書いてみました。

use std::fs::{read_to_string, File};
use std::io::Write;

fn load_file(path: &str) -> std::io::Result<String> {
    read_to_string(path)
}

fn save_file(path: &str, contents: &str) -> std::io::Result<usize> {
    File::create(path)?.write(contents.as_ref())
}

fn main() -> std::io::Result<()> {
    let contents = load_file("Cargo.toml")?;

    match save_file("Cargo2.toml", &contents) {
        Ok(size) => eprintln!("Write size: {:?}", size),
        Err(err) => eprintln!("Error: {:?}", err)
    };

    Ok(())
}

Formatterの設定

rustfmtを使います。
rustupでインストール時に同梱されているはず。

IDEAでは以下のように設定します。

ファイル保存時に自動でフォーマットされます👍

Linterの設定

rust-clippyを使います。
rustupでインストール時に同梱されているはず。

IDEAでは以下のように設定します。

ファイル変更時に今まで出なかった警告が出るようになります。
Result周りや所有権などですね👍😄

HHKBの有線化

家で使用しているHappy Hacking Keyboard Professional HYBRID Type-Sを無線接続から優先接続に切り替えました。

キーのつっかえや入力順の入れ替わりが頻繁にあったので調べたところ、Bluetoothの信号伝搬による問題であることが分かったからです。
有線と無線それぞれについて、Vimでhとlを交互に連打したときの挙動の違いで検証しました。

PCからはスピーカーにBluetoothを使っているので、それが干渉したせいかもしれません..。
ケーブルレスの佇まいが気に入っていたので残念ですが、こればかりは仕方ないかなと..😅

なお、会社ではBluetoothと有線の違いはありませんでした。
やはり環境が大きく影響するようですね。

今週のリリース

Owlelia v0.10.0

OwleliaはTypeScriptでDDD実装を補助するUtilityです。

2020-06-24現在では以下のような機能を提供しています。

  • Value Object
  • Entity
  • Either
  • Error

Either.orNull

leftの場合にnullを返却します。
既に実装済みであるEither.orUndefinedのnull版です。

import { left } from "owlelia";

left("hoge").orNull()
// -> null

Either.biMap

leftとrightそれぞれについて、functorを指定して変換します。

import { Either, left, right } from "owlelia";

const createEither = (num: number): Either<string, number> =>
  num > 0 ? right(num) : left("invalid error");

createEither(10).biMap(
  (err) => `${err} です`,
  (val) => val * 100
)
// -> Right { value: 1000, _type: 'right' }

createEither(-10).biMap(
  (err) => `${err} です`,
  (val) => val * 100
)
// -> Left { error: 'invalid error です', _type: 'left' }

Togowl v1.14.0

新機能は2つです。その他はレイアウト修正やリファクタリングです。

カレンダーを単独メニューに切り出し

今までTop画面のタブにあったカレンダーを単独メニューに切り出しました。
頻繁に計測画面と切り替えることがないため、画面の広さを活かすようにしています。

デジタルクロック

タスク未計測時、計測履歴カードの代わりにデジタルクロックを表示するようにしました。

その他

創の軌跡ムービー

創の軌跡発売日まで2ヶ月を切りました。
今回公開されたデモムービーは3分以上あり、テンションも上がってきています😆