Object.entriesの戻り値の型を厳密にするためにNegated typesが欲しい


Object.entries() - JavaScript | MDN

Object.entries  key  value  Javascript 
 Typescript 使
type Hoge = {
    a: string;
    b: number;
};

functionf(x: Hoge) {
    const entries = Object.entries(x);
    // entries の型は [string, string | number][]
    for (const [key, value] of entries) {
        // 処理
    }
}

使

zenn.dev

type Entries<T> = (keyofTextends infer U
    ? U extends keyof T
        ? [U,T[U]]
        : never
    : never)[];

function getEntries<Textends Record<string, unknown>>(obj: T): Entries<T> {
    return Object.entries(obj) as Entries<T>;
}

type Hoge = {
    a: string;
    b: number;
};

functionf(x: Hoge) {
    const entries = getEntries(x);
    for (const [key, value] of entries) {
        if (key === 'a') {
            console.log(value.toUpperCase());
        } else if (key === 'b') {
            console.log(value + 1);
        } else {
            // value は never
            throw new Error('should not get here.');
        }
    }
}

使 entries 使

 
Hoge  type  interface 
type Entries<T> = (keyofTextends infer U
    ? U extends keyof T
        ? [U,T[U]]
        : never
    : never)[];

function getEntries<Textends Record<string, unknown>>(obj: T): Entries<T> {
    return Object.entries(obj) as Entries<T>;
}

interface Hoge {
    a: string;
    b: number;
}

functionf(x: Hoge) {
    // const entries = getEntries(x);  // コンパイル通らない!
    const entries = Object.entries(x);  // これなら通るが、 entries の型は [string, any][]
    for (const [key, value] of entries) {
        // 処理
    }
}

 getEntries 
 Object.entries  [string, any][] 



 type  interface 

www.totaltypescript.com

Object.keys / Object.entries


TypeScript is a structural typing system. One of the effects of this is that TypeScript can't always guarantee that your object types don't contain excess properties:
type Func = () => {
  id: string
}

const func: Func = () => {
  return {
    id: '123',
    // No error on an excess property!
    name: 'Hello!',
  }
}

So, the only reasonable type for Object.keys to return is Array<string>.
 

TypeScript   Object.keys  Array<string> 

 Object.entries 
type Entries<T> = (keyofTextends infer U
    ? U extends keyof T
        ? [U,T[U]]
        : never
    : never)[];

function getEntries<Textends Record<string, unknown>>(obj: T): Entries<T> {
    return Object.entries(obj) as Entries<T>;
}

type Hoge = {
    a: string;
    b: number;
};

functionf(x: Hoge) {
    const entries = getEntries(x);
    for (const [key, value] of entries) {
        if (key === 'a') {
            console.log(value.toUpperCase());
        } else if (key === 'b') {
            console.log(value + 1);
        } else {
            // value は never
            throw new Error('should not get here.');
        }
    }
}

// 余剰のプロパティが含まれた変数
constx= { a: 'hoge', b: 42, c: { x: 13 }, };
// これは合法な呼び出し(結果として never の部分に到達して例外が投げられる)f(x);

 getEntries 
 interface 使

TypeScript  interface 使




teratail.com



 

 unknown 
type Entries<T> = /* 定義をここに書く */;

// スマートな型定義ができるなら Record である必要はない
function getEntries<Textends Object>(obj: T): Entries<T> {
    return Object.entries(obj) as Entries<T>;
}

interface Hoge {
    a: string;
    b: number;
}

functionf(x: Hoge) {
    const entries = getEntries(x);
    for (const [key, value] of entries) {
        if (key === 'a') {
            console.log(value.toUpperCase());
        } else if (key === 'b') {
            console.log(value + 1);
        } else {
            // value は unknown
            console.log(value);
        }
    }
}

  Entries<T> 
 TypeScript  string  
typeT= Exclude<string, 'a' | 'b'>;
// T は string から 'a' や 'b' を除いた型…ではない。 string になる

 Negated types 

github.com



github.com

 merge  close 





 Negated types  Entries<T> 
type Entries<T> = (
    (keyof T extends infer U
        ? U extends keyof T
            ? [U, T[U]]
            : never
        : never)
    | [string & not keyof T, unknown]
)[];

git 管理している dotfiles をインストールするスクリプトを書いてみた


gintenlabo.hatenablog.com





#!/usr/bin/env bash
# file: install-script.bash
set -ueo pipefail
cd "$(dirname "$0")"

CMDNAME=$(basename "$0")
print_usage() {
  cat - << EOF
usage: ${CMDNAME} (-n|-x) [options...]
    -n
        Executes dry run mode; don't actually do anything, just show what will be done.
    -x
        Executes install. This option must be specified if you want to install.
    -S <suffix>
        Specify backup suffix.
        If not given, BACKUP_SUFFIX is used; neither is given, '~' is used.
        This argument cannot be empty string.
EOF
}

MODE=
BACKUP_SUFFIX=${BACKUP_SUFFIX:-\~}

quote_each_args() {
  foriin $(seq 1 $#); do
    if[[ $i -lt $# ]]; then
      printf '%q ' "${!i}"
    else
      printf '%q' "${!i}"
    fi
  done
}
print_dry_run_message() {
  echo -e "will exec '$*'"
}
print_executing_message() {
  echo -e "executing '$*'..."
}
run() {
  if[[ "${MODE}" == 'dry-run' ]]; then
    print_dry_run_message "$(quote_each_args "$@")"
  else
    print_executing_message "$(quote_each_args "$@")"
    "$@"
    echo 'done.'
  fi
}

while getopts 'nxoS:u:m:' opt; do
  case $opt inn) MODE='dry-run' ;;x) MODE='execute' ;;S) BACKUP_SUFFIX="$OPTARG" ;;
    *) print_usage >&2
       exit 1 ;;
  esac
done
if[[ -z "${MODE}" || -z "${BACKUP_SUFFIX}" ]]; then
  print_usage >&2
  exit 1
fi

# init submodules
run git submodule update --init

# create symbolic links
echo
./install-script-tools/ls-linking-files.bash | while read -r file; do
  run ln -srvb -S "${BACKUP_SUFFIX}" -T "${file}" "${HOME}/.${file}"
done

# ここに残った初期化処理を書く( .gitconfig.local ファイルの作成とか vim の設定とか)
# 今回は省略
#!/usr/bin/env bash
# file: install-script-tools/ls-linking-files.bash
set -ueo pipefail

cd "$(dirname "$0")/.." # move to project root

LINK_IGNORE=${LINK_IGNORE:-.linkignore}

CMDNAME=$(basename "$0")
print_usage() {
  cat - << EOF
usage: ${CMDNAME} [options...]
    -v
        Verbose mode; print tracked files and ignored files.
    -h
        Print this message and exit.
EOF
}

VERBOSE=

while getopts 'vh' opt; do
  case $opt inv) VERBOSE='on' ;;h) print_usage
       exit ;;
    *) print_usage >&2
       exit 1 ;;
  esac
done

remove_directory_contents() {
  cat - | sed 's/\/.*//g' | sort -u
}

TRACKED_FILES=$(git ls-files -c | grep -v '^\.')
if[[ -n "${VERBOSE}" ]]; then
  echo "tracked files:" >&2
  echo -e "${TRACKED_FILES}\n" >&2
fi

IGNORED_FILES=$(git ls-files -ic --exclude-from="${LINK_IGNORE}")
if[[ -n "${VERBOSE}" ]]; then
  echo "ignored files:" >&2
  echo -e "${IGNORED_FILES}\n" >&2
fi

echo "${TRACKED_FILES}" | grep -vxFf <(echo "${IGNORED_FILES}") | remove_directory_contents
# file: .linkignore
# install-script でリンクさせたくないファイル/ディレクトリをここに置く
# 形式は .gitignore と一緒
# サブディレクトリ内のファイルは関係ないので、誤解なきよう原則として / 始まりで指定すること
/README*
/install-script*


使
$ ./install-script.bash -n



$ ./install-script.bash -x


~/.zshrc~

#!/usr/bin/env bash
# file: install-script-tools/restore-dotfiles-from-backup.bash
set -ueo pipefail
WORKDIR=$(pwd)
cd "$(dirname "$0")"

CMDNAME=$(basename "$0")
print_usage() {
  cat - << EOF
usage: ${CMDNAME} (-n|-x) [options...] [files...]
    -n
        Executes dry run mode; don't actually do anything, just show what will be done.
    -x
        Executes restoration. This option must be specified if you want to restore.
    -d
        Deletes given file if no backup found.
    -S <suffix>
        Specify backup suffix.
        If not given, BACKUP_SUFFIX is used; neither is given, '~' is used.
        This argument cannot be empty string.
    files
        Specify file paths to restore.
        If not given, files would be linked by install-script and ~/.gitconfig.local is restored.
EOF
}

MODE=
DELETE=
BACKUP_SUFFIX=${BACKUP_SUFFIX:-\~}

quote_each_args() {
  foriin $(seq 1 $#); do
    if[[ $i -lt $# ]]; then
      printf '%q ' "${!i}"
    else
      printf '%q' "${!i}"
    fi
  done
}
print_dry_run_message() {
  echo -e "will exec '$*'"
}
print_executing_message() {
  echo -e "executing '$*'..."
}
run() {
  if[[ "${MODE}" == 'dry-run' ]]; then
    print_dry_run_message "$(quote_each_args "$@")"
  else
    print_executing_message "$(quote_each_args "$@")"
    "$@"
    echo 'done.'
  fi
}
restore() {
  for file in "$@"; do
    local backup="${file}${BACKUP_SUFFIX}"
    if[[ -e "${backup}" ]]; then
      run rm -f "${file}"
      run mv -T "${backup}" "${file}"
    elif [[ -n "${DELETE}" ]]; then
      run rm -f "${file}"
    fi
  done
}

while getopts 'nxS:d' opt; do
  case $opt inn) MODE='dry-run' ;;x) MODE='execute' ;;S) BACKUP_SUFFIX="$OPTARG" ;;d) DELETE='on' ;;
    *) print_usage >&2
       exit 1 ;;
  esac
done
if[[ -z "${MODE}" || -z "${BACKUP_SUFFIX}" ]]; then
  print_usage >&2
  exit 1
fi

shift $((OPTIND - 1))

if[[ $# -eq 0 ]]; then
  ./ls-linking-files.bash | while read -r filename; do
    restore "${HOME}/.${filename}"
  done
else
  (cd "${WORKDIR}" && restore "$@")
fi
$ ./install-script-tools/restore-dotfiles-from-backup.bash -x ~/.zshrc




 Mac ln使
 Mac blog 




github.com



WSL Ubuntu に Oh my zsh と asdf をインストールしつつ、 .zshrc とかを git で管理するようにする

社内 Advent Calendar 向けに書いた記事ですが、社内のみに公開しておくのも勿体ないので全体公開するついでに blog にもリンク張っておきます。

gist.github.com

また、筆者が使っている dotfiles の GitHub repository も共有しておきます:
github.com

master は内容が古いので wsl-ubuntu ブランチが参考になるかと思います。

参考にして頂ければ幸い。

TypeScript で Haskell の comparing を実装してみた

https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-Ord.html#v:comparing

たぶん既出(調べるの面倒で調べていない


実装:

function compareAsc<T>(x: T, y: T): number {
  return x < y ? -1 : y < x ? 1 : 0;
}
function compareDesc<T>(x: T, y: T): number {
  return -compareAsc(x, y);
}

function comparing<T, R>(
  f: (x: T) => R,
  compare: (x: R, y: R) => number = compareAsc,
): (x: T, y: T) => number {
  return (x, y) => compare(f(x), f(y));
}

function comparingWith<Obj, Key extends keyof Obj>(
  key: Key,
  compare: (x: Obj[Key], y: Obj[Key]) => number = compareAsc,
): (x: Obj, y: Obj) => number {
    return comparing((obj: Obj) => obj[key], compare);
}


使い方:

const arr = [
  {a: 1, b: 2},
  {a: -1, b: 4},
  {a: 0, b: 6},
  {a: 23, b: 8},
];

// obj.a でソート
console.log([...arr].sort(comparing(({a}) => a)));
// この場合はcomparingWithで楽に書ける
console.log([...arr].sort(comparingWith('a')));

// b で逆順
console.log([...arr].sort(comparingWith('b', compareDesc)));
// a + b でソート
console.log([...arr].sort(comparing(({ a, b }) => a + b)));


多分だけど既存のパッケージあります。 知ってたら教えてください。

TypeScript で API 呼び出し結果をキャッシュするクラスを作ってみた

実装:

class CachedAsyncStore<T, Key = string> {
  private promiseMap: Map<Key, Promise<T>> = new Map();
  private fn: (key: Key) => Promise<T>;

  constructor(fn: (key: Key) => Promise<T>) {
    this.fn = fn;
  }

  get(key: Key): Promise<T> {
    const { promiseMap } = this;
    const cachedPromise = promiseMap.get(key);
    if (cachedPromise != null) {
      return cachedPromise;
    }
    const newPromise = this.fn(key);
    promiseMap.set(key, newPromise);
    return newPromise;
  }
  deleteCache(key: Key): boolean {
    return this.promiseMap.delete(key);
  }

  async getOrRetry(key: Key): Promise<T> {
    const { promiseMap } = this;
    for (;;) {
      const cachedPromise = promiseMap.get(key)
      if (cachedPromise == null) {
        break;
      }
      try {
        const result = await cachedPromise;
        return result;
      } catch (e) {
        // retry
        if (cachedPromise === promiseMap.get(key)) {
          promiseMap.delete(key);
        }
      }
    }
    const newPromise = this.fn(key);
    promiseMap.set(key, newPromise);
    return newPromise;
  }

  clearCache(): void {
    this.promiseMap = new Map();
  }
}


使い方:

// コンストラクタに取得関数を渡す
const store = new CachedAsyncStore<{ value: string }>(async (key) => {
  console.log(`API called: key: ${key}`);
  await new Promise<void>(resolve => setTimeout(resolve, 1000));
  if (key === 'error') {
    throw new Error('error!');
  }
  return {
    value: key,
  }
});

(async () => {
  // get で API を呼び出す
  console.log(await store.get('hoge'));
  // 既に呼び出されたことがあった場合はキャッシュが使われる
  console.log(await store.get('hoge'));

  // 並列で呼び出しても API は1回のみ呼ばれる
  console.log(await Promise.all([
    store.get('fuga'),
    store.get('fuga'),
    store.get('fuga'),
  ]));

  // エラーの場合
  try {
    await store.get('error');
  } catch (e: any) {
    console.error(e.message);
  }
  // get だと API 呼び出しは再試行されずにエラーになる
  try {
    await store.get('error');
  } catch (e: any) {
    console.error(e.message);
  }

  // エラーなら再試行したい場合は getOrRetry を使う
  // 並列で呼び出した場合でも直列化されるオマケつき
  console.log(await Promise.allSettled([
    store.getOrRetry('error'),
    store.getOrRetry('error'),
    store.getOrRetry('error'),
    store.getOrRetry('error'),
  ]));
})();


動機:

// 素朴な実装の場合
class SimpleCachedAsyncStore<T, Key = string> {
  private resultMap: Map<Key, T> = new Map();
  private fn: (key: Key) => Promise<T>;

  constructor(fn: (key: Key) => Promise<T>) {
    this.fn = fn;
  }

  async get(key: Key): Promise<T> {
    const { resultMap } = this;
    const cachedValue = resultMap.get(key);
    if (cachedValue !== undefined) {
      return cachedValue;
    }
    const newValue = await this.fn(key);
    resultMap.set(key, newValue);
    return newValue;
  }
  deleteCache(key: Key): boolean {
    return this.resultMap.delete(key);
  }

  clearCache(): void {
    this.resultMap = new Map();
  }
}

const store = new SimpleCachedAsyncStore<{ value: string }>(async (key) => {
  console.log(`API called: key: ${key}`);
  await new Promise<void>(resolve => setTimeout(resolve, 1000));
  if (key === 'error') {
    throw new Error('error!');
  }
  return {
    value: key,
  }
});

(async () => {
  // get で API を呼び出す
  console.log(await store.get('hoge'));
  // 直列なら問題ない(キャッシュされた値が使われる)
  console.log(await store.get('hoge'));

  // 並列で呼び出した場合に API が複数回実行されてしまう
  console.log(await Promise.all([
    store.get('fuga'),
    store.get('fuga'),
    store.get('fuga'),
  ]));

  // エラーの場合
  try {
    await store.get('error');
  } catch (e: any) {
    console.error(e.message);
  }
  // 直列でも毎回呼び出される(エラー処理してないため)
  try {
    await store.get('error');
  } catch (e: any) {
    console.error(e.message);
  }

  // 並列の場合はエラーになるのを待たずに API が実行される
  console.log(await Promise.allSettled([
    store.get('error'),
    store.get('error'),
    store.get('error'),
    store.get('error'),
  ]));
})();


既知の問題点:

  • fn を呼び出す際に this が CachedAsyncStore のインスタンスになってしまう(本来はコンストラクタなりで thisArg を受け渡すべきなのだが、面倒なのでやっていない)
  • key 以外の引数をコールバックに渡せない( Key をオブジェクトにすると中身は比較されずインスタンス毎に別の値としてキャッシュされてしまう)
  • 1つの Promise の値に対して何回も await することになるが、規格上それで問題ないのかを調べていない(筆者の母語C++ なので未定義動作が怖い)

他に問題点が ありましたら指摘をお願いします。

GCPのログエクスプローラで単語単位でエラーメッセージを検索したい場合は正規表現で \b を使えばいい



GCP使調調


textPayload =~ "\bword\b"

 \b 

M:tG職工スタンダード第1回世界大会優勝レポ

無謀なる衝動
つよい

機械兵団の進軍の発売を目前に控えた今日この頃、皆様はどうお過ごしだろうか。
今回は 3/26(日) に池袋で行われた職工スタンダードの大会で幸運にも優勝することができたため、デッキ選択や当日のマッチアップ、握ったデッキについて解説したいと思う。
拙い文章ではあるが、最後までお付き合い頂ければ幸い。


職工スタンダードの特徴とデッキ選択

職工スタンダードとは、現行スタンダードのカードプールの中で、希少度がコモンまたはアンコモンに設定されているカードのみを使用可能なフォーマットである。
レアや神話レアが使えないスタンダード、と言い換えても良い。

鏡割りの寓話黙示録、シェオルドレッド偉大なる統一者、アトラクサ
こいつらは使えない

使 M:tG 

使




1




使



使使










2使

使



3


赤単

www.hareruyamtg.com

アグロデッキの定番。 低マナ域の優秀なクリーチャーを展開し、火力で道をこじ開けつつライフを削り、最後の数点は火力を本体に撃ち込むことで削り切る。

ラクドスサクリファイス

www.hareruyamtg.com

シナジーデッキの代表格。 神河:輝ける世界を始めとした多くのエキスパンションで黒と赤の組み合わせに与えられている生け贄シナジーを活かし盤面を構築する。

青黒増殖コントロール


www.hareruyamtg.com

 


 

















赤単バーンの構築と調整




mtg-jp.com














表現の反復

322




23 使

*1







31

mtg-jp.com
mtg-jp.com
mtg-jp.com
mtg-jp.com


 
3/3


mtg-jp.com

3

mtg-jp.com
mtg-jp.com
mtg-jp.com

3+1/+1*2

2

mtg-jp.com



233

mtg-jp.com

43


4

mtg-jp.com





mtg-jp.com

214/34/31 




当日のマッチアップ

そんなこんなで持ち込んだ赤単バーン。 大会の参加者は12名、スイスラウンド4回戦のちトップ4によるシングルエリミネーションで争われることになった。

第1ラウンド


 LWL


123
117



第2ラウンド


 WW

1
12


第3ラウンド

対 青黒増殖、先手、WW

コントロールではない、毒で押し切るタイプの青黒増殖とマッチアップ。
火力でお相手のクリーチャーを捌いて勝利。

第4ラウンド


 LWW

 
1
2
31 


3-14


準決勝


 WW


 () (@kitaro_yureich) 2023326
 

 

 


決勝


 LWW


 () (@kitaro_yureich) 2023326
 

 *3

1 
2
378



勝因と今後の課題



4








最後に




 
調

*1:プレイヤーにも飛ぶ火力

*2:ただし炎カウンターに関しては注意が必要で、最初の炎カウンターはダメージを与えたことで乗るため《電位の負荷》単体では増殖できない

*3:最初に負けた後に全勝して決勝SEに残ること