2023-02-10
Web Serial API は、ウェブサイトがシリアルデバイスからデータを受信したり、シリアルデバイスにデータを送信したりする方法を提供します。対象は、シリアルポートで接続されたデバイスのことも、シリアルポートとして振る舞う USB や Bluetooth のデバイスのこともあります。MDN
視線を戻す
GUI開発と破壊的更新を避ける動き
Java 周辺には「実装の再利用を目的とした継承」を避ける方 向への動きが見られます。たとえばグラフィックツールキットの実装がそ うです。1995年にリリースされたAbstract Window Toolkit(AWT)では「継承して各種メソッドをオーバーライドして使う」ルールでした。しかし、今 Eclipseなどで使われているStandard Widget Toolkit(SWT)は「継承してはいけない」というルールです。
代わりに発達したのが「委譲」の概念です
Hooks APIと関数コンポーネント
リアクティブプログラミング系の流れ
// Specify the data source.
int[] scores = { 97, 92, 81, 60 };
// Define the query expression.
IEnumerable<int> scoreQuery =
from score in scores
where score > 80
select score;
// Execute the query.
foreach (int i in scoreQuery)
{
Console.Write(i + " ");
}
// Output: 97 92 81
- [Language-Integrated Query (LINQ) (C#) | Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/)
- 配列などのコレクションに対してもSQLのようにクエリランゲージを書ける
int sum = widgets.stream()
.filter(w -> w.getColor() == RED)
.mapToInt(w -> w.getWeight())
.sum();
- [Stream (Java Platform SE 8)](https://docs.oracle.com/javase/jp/8/docs/api/java/util/stream/Stream.html)
- もちろん、各メソッドの呼び出しで大きな一時オブジェクトが作られたりはしない
public record Person(string FirstName, string LastName);
- [レコード・クラス](https://docs.oracle.com/javase/jp/15/language/records.html)
java
record Rectangle(double length, double width) { }
- > レコード・クラスは一連のフィールドを宣言し、適切なアクセッサ、コンストラクタ、equals、hashCodeおよびtoStringメソッドが自動的に作成されます。このクラスは単純な「データキャリア」として機能することを意図しているため、フィールドはfinalです。
- > この簡潔な宣言は、次の標準クラスと同等です:
java
public final class Rectangle {
private final double length;
private final double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double length() { return this.length; }
double width() { return this.width; }
// Implementation of equals() and hashCode(), which specify
// that two record objects are equal if they
// are of the same type and contain equal field values.
public boolean equals...
public int hashCode...
// An implementation of toString() that returns a string
// representation of all the record class's fields,
// including their names.
public String toString() {...}
}
- Q: すごく単純なstruct?
- A: 破壊的更新が禁止されてるところがstructとの大きな違い
- Q: 先に代入することができない?単純にデータをトランスファーするオブジェクト?
- A: そう。そういうオブジェクトを作る手段が今まではたくさんコードを書かないと実現できなかった。
- 2009年とかの発展の過程で「ここを伝わるオブジェクトが破壊可能なのはおかしいよね」「破壊不可能なオブジェクトが手軽にかける手段が必要だよね」ということでJavaとC#が変わった
- Q2: const dataってことですか
- A2: そう言っていいと思います
Q3: ミューテックスだけは可変に持ちたいという欲が出てくると思う
先ほどのReactで関数コンポーネントが仮想DOMを返していたのも同じ
ざっくりまとめ
An open standard for sound, interoperable JavaScript promises—by implementers, for implementers.
promise2 = promise1.then(onFulfilled, onRejected);
// callback style
do_something(params, on_success, on_error);
js
r = new Request(params);
r.on_success = on_success;
r.on_error = on_error;
r.send()
- かつてのJavaScriptでは他のサービスのAPIを叩いたりするのに`XMLHttpRequest`を使っていた、これはイベントハンドラを使うスタイルだった
- [Using XMLHttpRequest - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest)
- これが面倒だったのでサードパーティライブラリが試行錯誤し、Promiseを使うスタイルがデファクトスタンダードになった
- そのあとでPromiseを使うスタイルのAPIが標準化された
- [Fetch API - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
- 何が面倒だったか?色々あるが:
- 非同期処理をした後にやりたいことをソースコード上で先に書くのは思考の流れが混乱する
- 非同期処理1をした後、その結果を使って非同期処理2をして…と続けて行った場合のエラー処理
- [Promises/A+](https://promisesaplus.com/)でこう定義されているので、1箇所にまとめて書くことができる
- > If `onRejected` is not a function and `promise1` is rejected, `promise2` must be rejected with the same reason as `promise1`.
- コールバックやイベントハンドラのスタイルでは、名前付きの関数にした上であちこちの`on_error`にそれを指定することになる
def foo(x: int):
y: str = x # NG
- `error: Incompatible types in assignment (expression has type "int", variable has type "str") [assignment]`
java.lang.Objectみたいな型?def foo(x: int):
y: object = x # OK
python
def foo(x: object):
y: str = x # NG
- `error: Incompatible types in assignment (expression has type "object", variable has type "str") [assignment]`
- Object型の値をString型の値に代入しようとすること(暗黙のダウンキャスト)はNG
- これが普通の型の振る舞い
- anyがアップキャストもダウンキャストも暗黙に行える特殊な型ということ
- 
- TypeScriptではこの二つの挙動に対応する型が unknownとany
ts
const take_number = (x: number): void => {};
{
let x: any;
take_number(x); // OK
}
{
let x: unknown;
take_number(x); // NG
}
- `Argument of type 'unknown' is not assignable to parameter of type 'number'.`
def foo(x: int):
return x
print(typing.reveal_type(foo))
- `note: Revealed type is "def (x: builtins.int) -> Any"`
- これはTypeScriptとは異なる挙動
- 
- 人間がこう明示することが期待されている
python
def foo(x: int) -> int:
return x
print(typing.reveal_type(foo))
- `note: Revealed type is "def (x: builtins.int) -> builtins.int"`
名前的型付けと構造的型付け
int n;などと宣言することで「これは整数だよ」という情報を変数につけるのが静的型付けこの「変数にメタ情報をつけるアプローチ」に二つある
1985年に生まれたC++や1995年に生まれたJavaは名前的型システムを採用していた。
extendsやimplementsで部分型関係の宣言をする一方、型理論の研究においては構造的型システムの方が主流だった。
TypeScriptでの例 TypeScript
type T1 = {
x : number
}
type T2 = {
x : number
}
let a: T1;
let b: T2 = {x: 1};
a = b; // OK
- このコードの`let b:T2 = {x: 1}`の、`{x: 1}`の部分は名前のないオブジェクト型である。
- これはT1ともT2とも同じ形をしているので、エラーになることなく代入できる。
Rust: 所有権・ライフタイム
std::unique_ptrが採用されているlet x = String::from("hello");
let y = x;
println!("x: {}", x); // NG
println!("y: {}", y);
- `error: borrow of moved value: x`
- xやyがC++でいうところのunique_ptrになっていて、`y = x`でムーブが行われている
- C++では普通は参照のコピーをしようとしてしまう
- 「コピーではなくムーブを意図しているんだ」と表現するために`std::move`を使う
- これを既存のコードを壊さずに実現するために「右辺値/左辺値」の区別がC++11で追加された
- 「ある値が右辺値であるなら、今後使わないのでコピーではなくムーブでよい」ということ
- こうして右辺値参照を受け取るムーブコンストラクタが追加された
- std::moveは右辺値参照にキャストすることでムーブが行われるようにする
#include <iostream>
#include <string>
#include <memory>
int main () {
std::unique_ptr<std::string> x = std::make_unique<std::string>(std::string("abc"));
std::unique_ptr<std::string> y;
y = std::move(x);
std::cout << *y << std::endl;
std::cout << *x << std::endl; // here
}
output
abc
Segmentation fault
- このコードでは、間違って読んだり解放したりしてしまわないようにxはnullになっている
let x = String::from("hello");
foo(x); // move
println!("x: {}", x); // NG
- この例では関数呼び出しのところでxがムーブしているからそれ以降使えない
- 代入によるムーブや関数呼び出しによるムーブをたどっていって「ムーブせずにスコープを抜けたところ」で解放をするようにコンパイラがやってくれる
ライフタイムの概念
let x;
let y;
// println!("x: {}", x); // NG
// error[E0381]: used binding `x` is possibly-uninitialized
x = String::from("hello");
println!("x: {}", x); // OK
y = x;
// println!("x: {}", x); // NG
// error[E0382]: borrow of moved value: `x`
- xの初期化前とムーブ後の利用がコンパイルエラーになることがわかる
'aとつけるのは、ジェネリクスで型変数にTと名前をつけるのと同じrust
fn longest(x: &str, y: &str) -> &str { // NG
if x.len() > y.len() {
x
} else {
y
}
}
error
error[E0106]: missing lifetime specifier
--> src/main.rs:14:33
|
14 | fn longest(x: &str, y: &str) -> &str {
| ---- ---- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
|
14 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
| ++++ ++ ++ ++
- 例2
- staticライフタイム
- あらかじめシステムによって名前が付いているライフタイム指定子
- 「プログラム全体にわたるライフタイム」を表現している
rust
fn dangle() -> &String {
let s = String::from("hello");
&s
}
error
error[E0106]: missing lifetime specifier
--> src/main.rs:22:16
|
22 | fn dangle() -> &String {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
|
22 | fn dangle() -> &'static String {
| +++++++
- これは本来はダメ
- 関数を抜ける時にsが解放されるから
- なんだけどコンパイラが「定数に変えたらプログラム全体をライフタイムにできるな」と判断してる
- その結果2歩先を行くエラーメッセージになってる
- コンパイラが定数にできるかわからないようにしてやれば素直なエラーメッセージになる
rust
fn dangle(x: &String) -> &String {
let s = x.clone();
&s
}
error
error[E0515]: cannot return reference to local variable `s`
--> src/main.rs:31:5
|
31 | &s
| ^^ returns a reference to data owned by the current function