2023/08/01
はじめに
2022年度の大学のプロジェクトで(今更すぎる)手書き風の画像を生成するWebサービスを開発したのでそれについての話です。
リンク
tegaki.fun
サービスのURL
nkmr-lab/average-character-cloud-backend
バックエンドのリポジトリ
nkmr-lab/average-character-cloud-frontend
フロントエンドのリポジトリ
nkmr-lab/average-figure-drawer
平均手書き文字(手書き文字の合成文字)を生成するライブラリのリポジトリ。研究室で数年前に作られた物をTypeScript化(不完全)するなどしただけで、上2つと違って1人で書いたわけではない。
サービスの概要
下の画像のように、上のテキスト欄に文字列を入力すると、その文字列に対応する手書き風の画像を生成してくれます。背景画像はユーザが用意してきて、それに合わせて文字サイズや書字方向、行間字間などを設定する仕様です。各文字は、ユーザが登録した手書き文字を複数組み合わせて平均手書き文字とい
2020/09/16
対象読者
明大FMSのEP演習という授業で発表した内容の補足資料なので、同大の同級生を対象読者に基本的なところから解説しています。言語実装をしたことある人にとっては知っている話が多いかと思います。
ソースコード
https://github.com/kgtkr/BarrageLCL
ドキュメントもここにあります。今回はd8d1412をベースに解説していきます。
BarrageLCLの概要
Processing製の3D弾幕生成ライブコーディング言語です。実行環境だけでなく、編集環境も用意されています。自作言語×自作エディタですね。
以下のコードを見てください。
このコードは毎秒15回、初速度を (100cos(0.2i), 100sin(0.2i), 200sin(0.1i)) に、色を赤に設定して弾を発射するというコードです。i は何回目の実行かを表す変数です。
すると以下のように弾が発射されます。
なぜこれを?
EP演習第一回のオープニングパフォーマンスで見たライブコーディング言語が面白かったことと、夏休みのEP演習で3D空
2020/09/02
初めに
TypeScriptの型システムはチューリング完全なので何でも計算できます。
例えば繰り返し。
type Repeat<T, N extends number, R extends any[] = []> = R["length"] extends N
? R
: Repeat<T, N, [T, ...R]>;
// type A = ["x", "x", "x", "x", "x", "x", "x", "x", "x", "x"]
type A = Repeat<"x", 10>;
わーい。ちょっと数増やすか…
// error TS2589: Type instantiation is excessively deep and possibly infinite.
type A = Repeat<"x", 100>;
あれ?(つらい)
再帰制限解除したいですね。しましょう。
注意
当然ですがプロダクトで使うことは想定していません、やめましょう。
バージョンなど
typescript@4.1.0-dev.
2020/02/24
pick関数
lodashなどにはオブジェクトと抽出するプロパティ名の配列を受け取り、指定されたプロパティのみを含むオブジェクトを返すpick関数が存在します。以下のような関数です。
// { a: 1, c: 3 }
pick({ a: 1, b: 2, c: 3 }, ["a", "c"])
今回はこの関数に安全な型定義をすることを考えていきます。
単純な型定義とその問題点
まず思いつくのは以下のような型定義だと思います。
declare function pick<A, K extends keyof A>(obj: A, keys: K[]): Pick<A, K>;
// ex1: { a: number }
const ex1 = pick({ a: 1, b: 2, c: 3 }, ["a"]);
// ex2: { a: number, b: number }
const ex2 = pick({ a: 1, b: 2, c: 3 }, ["a", "b"]);
// ex3: {}
const ex3 = p
2020/02/06
以下のコードを見てください。
type HogeUnion = 1 | 2;
function printHogeUnion(x: HogeUnion) {
console.log(x);
}
HogeUnion型、つまり1 | 2であるxを受け取って出力するだけの単純な関数printHogeUnionを定義しています。何も問題がないコードです。
もしTSの型の健全性が壊されてxに1 | 2以外の値が入ってきた時にすぐ気付けるようにランタイム型チェックを行いたくなったとしましょう。例えばprintHogeUnion(0 as any)を実行すると0を出力するのではなく例外を投げるといった具合です。この時unreachable関数を定義し、printHogeUnionにランタイム型チェックのコードを追加して以下のようになります。
function unreachable(): never {
throw new Error("unreachable");
}
function printHogeUnion(x: HogeUni
2019/08/27
初めに
この記事は型安全な型定義とそれが可能な実装に関する考察であり利用者側にとっての使いやすさなどは考慮していません。ここに型安全ではない例としてあげた、もしくそれと同じような定義となっているライブラリも型推論に頼っていれば基本的に安全な事と、使いやすさを考えると仕方ない部分があることは理解しておりそのようなライブラリに対する批判でもありません。
曖昧な知識で考えた自分用メモのようなものなので間違っている所がある可能性は高いですし参考にはしないで下さい。間違ってる所があればTwitterなどで指摘してくださると嬉しいです。
オブジェクトのキーの集合の値表現
以下のような関数定義があるとする。
function f<T extends object, K extends keyof T>(obj: T, keys: Keys<K>): Result<T, K>;
この時型コンストラクタResultの性質によって場合分けし、Keys、つまりオブジェクトのキー集合の安全な受け取り型を考える。
またobj: Tとkeys: Keys<K>を
2019/07/16
TL;DR
import { isT, isNotT, defineIsT } from "safe-type-predicate";
// isA: (x: "a" | "b") => x is "a"
const isA = defineIsT((x: "a" | "b") =>
x === "a" ? isT(x) : isNotT()
);
GitHub
safe-type-predicate
type predicate and its dangers
TypeScript has a feature called type predicate.
This is by coding the return value of a function that takes an appropriate type of value x and returns boolean as x is T. If true is returned, x is of type T, false This is a function that
2019/07/16
TL;DR
import { isT, isNotT, defineIsT } from "safe-type-predicate";
// isA: (x: "a" | "b") => x is "a"
const isA = defineIsT((x: "a" | "b") =>
x === "a" ? isT(x) : isNotT()
);
GitHub
safe-type-predicate
type predicateとその問題点
TypeScriptにはtype predicateという機能が存在する。
これは適当な型の値xを受け取りbooleanを返す関数の戻り値をx is Tと書くことで、trueを返せばxがT型、falseを返せばそうでないことを表す機能である。
これによってユーザー定義関数で型ガードを行うことを可能にしている。
例えばunknown型、つまり任意の型を受け取りその値がstring型であるかを返す関数は次のようになる。
function isString(x: unknown): x is
2019/04/15
When type level programming, various errors occur.So we need to suppress it.
This article introduces two ways to suppress errors.
I checked with v3.4.2.
Suppress type constraint error
There are operations in TS that can only be of the type that satisfies the condition.
Here are some examples.
type F<T extends any[]> = T;
type X = F<A>;// A require to extends any[]
type Y = B["x"]// B require object type and have roperty named `x`
However, even if it is known that the con
2019/04/15
TypeScriptの型レベルプログラミングでは様々な型エラーが発生するため、それを抑制しながら作っていかなければいけません。
今回はエラーを回避するテクニックを2つ紹介します。
v3.4.2で動作確認をしています。
型制約エラーを抑制する
TSには型の型のようなものがあり、それを満たす型にしかできないような操作があります。
いくつか例を挙げます。
type F<T extends any[]> = T;
type X = F<A>;// Aはany[]を継承していなければいけない
type Y = B["x"]// Bはオブジェクト型で`x`というプロパティを持っていなければいけない
しかしこのような条件を満たしていることが分かっていてもコンパイラが推論出来ておらずエラーになることがあります。
そのような時は以下のようなCast型を定義して使いましょう。
これはTはPを継承している時は分かっている時にCast<T, P>と書いて使います。
type Cast<T, P> = T extends P ? T : P;
例です。
2019/04/12
この記事はTS3.4.3で動作確認をしています。
ハイパー演算子とは
hyper(1,a,b)=a+b
hyper(2,a,b)=a*b
hyper(3,a,b)=a^b
︙
のような演算子です(雑)
詳しくはWikipediaを見て下さい。
定義だけ貼っておきます。
hyper(1,a,b)=a+b
hyper(_,a,1)=a
hyper(i,a,1)=hyper(i-1,a,hyper(i,a,b-1))
型レベルタプル操作
型レベルのタプル操作を行うので以下の記事を読んでおくといいかもしれません。
TypeScript 3.0のExtracting and spreading parameter lists with tuplesで遊ぼう
ちなみに今回は上の記事の型レベルタプル操作などをまとめた自作ライブラリtypeparkを使いますが機能は上の記事の通りです。
再帰制限回避テク
FやGが複雑な関数だと以下のような書き方をするとコンパイルエラーが発生することがあります。
type Hoge<T>=F<Cast<G<T
2019/04/11
TypeScriptのランタイム型チェック
TypeScriptにはランタイム型チェック機能がありません。
次のようなコードも正常にコンパイルされエラーが発生することなく動作します。
const x: string = JSON.parse("1");
これはパフォーマンスなどとのトレードオフなので仕方ないのですが、部分的に動的な型チェックをしたいときもあります。
このような時の解決策としてio-tsを紹介します。
io-tsのメリット
JSON Schemaなどは型とスキーマを2つ書く必要があり大変です。
また大変なだけでなく型とスキーマが異なるといったバグを型システムでチェックすることが出来ません。
コードジェネレーターを使うことで解決できますが、TypeScript上で完結しないのでめんどくさいです。
このような問題はio-tsを使うことで解決します。
ただし再帰型については型推論の関係で型とスキーマを2回書く必要がありますが、型チェックでスキーマと型が違っていればコンパイルエラーが発生します。
基本的な使い方
import
2018/10/02
Extracting and spreading parameter lists with tuples
TS3.0で追加されたタプル型をより強力に扱うための機能です。
型パラメーターとして受け取ったタプルを展開したり出来ます。
基本的な構文は解説しないので公式ブログあたりを見ておいて下さい。
参考
Tuples in rest parameters and spread expressions #24897
このPRで紹介されているものを主に解説していきます。
注意
TypeScript3.0.xにはバグがありコンパイラやエディタ(正格にはtsserver)がクラッシュすることがあるのでTypeScript3.1.xを使って下さい。
前提知識
TSの基本的な型システムの知識宣言的なリスト操作(Haskellなどでやるあれ)
用語
この記事では、type F<T>=...;を関数のように扱うので、Fを関数、Tを引数、タプル型をリスト、type V=...;を値と表現します。
紹介
Head
リストの先頭の要素を返す基本的な関数で
2018/09/18
はじめに
TypeScript(TS)には高カインド型(HKT)がありません。一応提案がありますが…
しかし様々な機能を組み合わせればHKTを実現することが出来るのでそれの方法と解説を行います。
この記事ではHKTとは何かなどといった解説は行いません。表記は*->*のような表記を使います。
またfp-tsというライブラリを参考にしています。
使う機能
interfaceのマージ(Declaration Merging)
TypeScriptでは同名のinterfaceを複数定義出来ます。
そしてこれらは自動的にマージされます。
例えば次の2つの宣言は同等です。
export interface Hoge {
x: number;
}
export interface Hoge {
y: number;
}
export interface Hoge {
x: number;
y: number;
}
そしてこの機能は別ファイルや別モジュールで既に定義されているinterfaceを拡張する事も出来ます
2018/09/10
やり方
準備
test.ts
export type TypeEq<A, B> = (<T>() => T extends A ? 1 : 2) extends (<
T
>() => T extends B ? 1 : 2)
? true
: false
export function assertType<_T extends true>() {}
export function assertNotType<_T extends false>() {}
色々複雑になっていますが、これは any や union、never などに対応するためです。
ここらへんの型は少し特殊なため、このようにしないと正常にテスト出来ません。
テストの書き方
assertType<TypeEq<1, 1>>()
assertNotType<TypeEq<1, 2>>()
テストが正しくなければコンパイルエラーが発生します。
例
import { TypeEq, assertType, assertNotType } from "./t
2018/09/03
spread operator
interface Tweet{
id:string;
user:string;
body:string;
}
//tweetはTweet型、userはUser型とする
let tw={...tweet,...{user:user}};
この時の変数twの型を定義したい時どうしますか?
素直に書いてみましょう
interface Tweet2{
id:string;
user:User;
body:string;
}
面倒ですね。プロパティが増えるともっと大変です。
typelevel-ts
typelevel-tsという便利なライブラリがあります。
このライブラリを使うとこう書けます。
import {ObjectOverwrite} from 'typelevel-ts';
type Tweet2=ObjectOverwrite<Tweet,{user:User}>;
これだけです。
他のライブラリ
type-zootypical
というライブラリもあります。
2018/06/23
初めに
TypeScript(以下TS)はJSに静的型システムを取り入れた言語です。
しかしTSの型システムには多くの穴があり、知らないと型の整合性を壊してしまいます。(型システムが健全でないという)
そこで今回はそのような操作をまとめてみました。
間違え、不足等があればコメントで指摘してくださると助かります。
この記事の目的
この記事は「TSの型システムの穴」を批判することが目的ではありません。
実行時のオーバーヘッドを無くすことや利便性などとのトレードオフであることは理解しています。
TSを書く多くの人が「このような操作をすると型の整合性が壊れることがある」ということを理解した上で使ってほしいというのがこの記事の目的です。
型システムの健全性とは
静的言語に限ると、「コンパイルが通ったなら実行時に型情報と値が矛盾しない事が保証されている」事を言います。
例えば「number型の変数に"hello"が入っている事は絶対にありえない」といった感じです。Javaなど多くの静的型付け言語の型システムは健全、つまりこのような事が保証されている
2018/01/01
初めに
継承が必要ない場合は他の方が書いた下記記事を参考にして下さい。
https://qiita.com/nwtgck/items/bbfd6e3ca16857eb9c34
必要な物
ts-copyable:上で紹介されていたnpmパッケージ
applyMixins関数:ここからutils.tsにコピペしておいて下さい。export忘れずに。
実装の方針
mixinを使う。
実装
import Copyable, { PartialMap } from "ts-copyable";
import { applyMixins } from "./utils";
export abstract class Base<C extends Base<C>>{
abstract readonly a: number;
abstract readonly b: string;
abstract copy(partial: Partial<Base<C>>): C;
abstract mapCopy(partial: Part
2017/12/02
strictNullChecks時の改善
今まではmapやforEach関数に渡すコールバックの第一引数がT?でしたが、v4ではTになりました。
これによってstrictNullChecks時でも!が不要になりました。
例:
//v3以前
x.map(x=>x!+1);
//v4
x.map(x=>x+1);
v3以前もxにundefinedやnullが渡されることはなかったのに型定義はT?で気持ち悪かったので修正されてよかったです。
Recordが型安全になった
const MyRecord = Record({ x:1,y:2 });
const myRecord = new MyRecord({});
//v3以前は以下のコードのコンパイルが通る
//v4では型エラーになる
myRecord.get('a');
今までは型安全じゃなかったのでRecordを使うのは避けていたのですが、これによって安心して使えるようになりました。
2017/11/06
普通に
普通に読み込むとエラーになります。
app.ts
import * as hoge from 'hoge';
推奨
ソースフォルダに適当な名前.d.tsファイルを作って以下の内容を書きます。
types.d.ts
declare module 'hoge';
あとは普通に読みこめばOKです。
app.ts
import * as hoge from 'hoge';
ワイルドカード
ワイルドカードも使えます。
types.d.ts
declare module '*';
非推奨
これでも一応出来ますが、あまりおすすめはしません。
app.ts
declare function require(path: string): any;
const hoge = require('hoge');
参考
https://github.com/Microsoft/TypeScript/issues/6615
2017/09/10
普通に書く
let arr1:(number|string)[]=['a','b','c',1,2,3];
let arr2=arr1.filter(x=>typeof x==='string');
arr2にはstringしか入ってないはずなのに型は(number|string)[]になってしまいます。
型ガード
let arr2 = arr1.filter<string>((x): x is string => typeof x === 'string');
これでarr2はstring[]になります。
注意
let arr2=arr1.filter<number>((x): x is number => typeof x === 'string');
このような事をしてもコンパイルエラーにはなりません。
arr2の実際の型はstring[]なのにTypeScriptはnumber[]と認識します。
キャストと同じように危険な操作なので注意しましょう。
他のライブラリ
immutable.jsやrxjsでも似たような事を出