関数のインターフェースから例外がわかる言語で書くフロントエンド in 2024
簡単に言うとResult型みたいなやつを使いたいという話です。Rust,Scala,Elm,moonbitあたりで検証します。
フロントエンド編
普段使用しているスタック
まずは普段使用しているスタックの代替となるライブラリや機能について探していきます。
ライブラリ | 目的としている機能 | Rustエコシステム標準搭載 |
---|---|---|
React | コンポーネントベースの開発 | x |
tanstack-query | データフェッチ時の状態管理 | x |
ts-results | リザルト型 | o |
zod | データフェッチング時のResponseのparseに使う | x |
eslint | linter | o |
prettier | formatter | o |
testing-library | コンポーネントのみでレンダリングし、さらにそのレンダリング結果へアクセスし検証する機能 | x |
vitest | テストを書く機能 | o |
MUI | 楽に使えるUIライブラリ | x |
FetchAPI | データフェッチング | x |
こうしてみてみるとRustのエコシステムの優秀さ、後発ならでは名の良さがよくわかります。というわけで標準搭載ではない以下のライブラリの代替を探していきます。
- データフェッチング周り
- React
- testing-library
- UIライブラリ
データフェッチング周り
一般的にwasmからブラウザのAPIを操作するときはweb_bindgenを使うみたいです。
フェッチングの例 (https://rustwasm.github.io/wasm-bindgen/examples/fetch.html を少し変えました)
api.github.comから{name: string}を取得してResponseData {name: string}にする例です。
zod.parseの代わりにserde_wasm_bindgenが使えるイメージです。parseした結果がresultで帰ってくるのもうれしいですね。
use serde_wasm_bindgen;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::console;
use web_sys::{Request, RequestInit, RequestMode, Response};
extern crate serde;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct ResponseData {
name: String,
}
#[wasm_bindgen]
pub async fn run(repo: String) -> Result<(), JsValue> {
// JSのRequestInitを再現したオブジェクトの作成
let mut opts = RequestInit::new();
// メソッドの設定
opts.method("GET");
// モードの設定
opts.mode(RequestMode::Cors);
// URLの作成
let url = format!("https://api.github.com/repos/{}/branches/master", repo);
// RequestInitとURLを使ってRequestオブジェクトを作成
let request = Request::new_with_str_and_init(&url, &opts)?;
// ヘッダーの設定
request
.headers()
.set("Accept", "application/vnd.github.v3+json")?;
// web sysを使ってwindowオブジェクトを取得
let window = web_sys::window().unwrap();
// window.fetch_with_request(&request)
// FetchAPIのFetch関数はfetch_with_requestメソッドで呼び出し
// JsFuture::from(window.fetch_with_request(&request))
// JsFutureでPromiseをFuture(RustのPromiseみたいなの)に変換
// JsFuture::from(window.fetch_with_request(&request)).await?;
// awaitでPromiseの結果を待つ
// resp_valueにはPromiseのresolveされた値が入るが、この時点ではJsValueというJsから来た型がわからない値になっている
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
// JsValueがResponse型に変換できるか確認
assert!(resp_value.is_instance_of::<Response>());
// JsValueをResponse型(ここでいうResponse型はWasm側が用意したJSライクなオブジェクトのこと)に変換
let resp: Response = resp_value.dyn_into().unwrap();
// Responseから値を取り出しす
// またここでもJsFutureを使ってPromiseをFutureに変換している
let json = JsFuture::from(resp.json()?).await?;
let object: ResponseData = serde_wasm_bindgen::from_value(json).unwrap();
console::log_1(&object.name.into());
// Send the JSON response back to JS.
Ok(())
}
}
Reactの代替
Dioxus
DioxusはReactみたいな感じで、コンポーネント思考で仮想DOMをいじれるSSR対応のRustライブラリです。
tailwind.cssを使うテンプレートも用意されており、Tauriと組み合わせたGitHubリポジトリもあり、Tauriのフロントエンドとしてよさそうです。
またReact-RouterのようなものもありしっかりSPAできそうな気がします。
とりあえずは先ほど実装してみたデータフェッチング込みで適当なアプリケーションを作っていきます。
想定外だったんですけど、どうやらFetchAPIを通さなくてもrequwestでデータフェッチングできるみたいです。
#![allow(non_snake_case)]
use dioxus::prelude::*;
use reqwest;
use serde::{Deserialize, Serialize};
use tracing::Level;
#[derive(Clone, Routable, Debug, PartialEq)]
enum Route {
#[route("/")]
Home {},
}
fn main() {
dioxus_logger::init(Level::INFO).expect("failed to init logger");
launch(App);
}
fn App() -> Element {
rsx! {
Router::<Route> {}
}
}
#[component]
fn Home() -> Element {
let data = use_resource(|| async move {
reqwest::get("https://api.github.com/repos/rustwasm/wasm-bindgen/branches/master")
.await
.unwrap()
.json::<ResponseData>()
.await
});
rsx! {
div {
match &*data.read_unchecked() {
Some(Ok(data)) => rsx!{div { "Data: {data.name}" }},
Some(Err(_)) => rsx!{div { "Err" }},
None => rsx!{div { "Loading..." }}
}
}
}
}
#[derive(Debug, Serialize, Deserialize)]
struct ResponseData {
name: String,
}
tanstack-queryのisLoadingとかそういうのがmatchで判定できるのがうれしいところですね。
ただcacheing周りがどうなるかはよくわかってないので要調査です。
Yew
こっちの方が歴史あるけど今イケイケで開発されているというよりは安定期に入ってるという感じです。
documentが日本語で読めるのがかなりうれしい。
とりあえずGetting startedを進めてみようとしたが、いきなりエラー。
ComponentLinkがなかったりShouldRenderがなかったりする。
0.21で0.17バージョンのGetting Startedが表示されるのはこれ如何に。
というわけでやっぱり英語からは逃げられない・・・(最新版の日本語ドキュメントはないみたい。)
cssについてはかなり充実しており、UIライブラリからtailwind, CSS in JSまである。
こっちでもとりあえずデータフェッチングをしてみる。
で調べてみた感じ0.17のfetchの例はあるんだけど、最新バージョン0.21ではその項目がドキュメントから消されているみたい。
消されているので、そのほかの方法を探したところSuspenceを使う方法があるようだ。
しかし、reqwestを使ったフェッチングをすると非同期処理が必要になるため、非同期処理をcustom hooks(yewではカスタムフックスが作成できる)でしたいのだがやり方がいまいちわからない
調べても基本0.17の手法しか見つからず、0.17はクラスコンポーネントっぽい書き方になるのでできれば使いたくはない。
調べた感じYew::Serviceというものにたどり着いた、これを使えば同期的なfetchを使えるが本質的な解決にはなってない上に最新版では消えていて、glooとかreqwest使えってなってる。
ここで救世主登場
どうやらJsのPromiseが来たらFutureになる都合で、async関数が必要だと思ってたが、wasm_bindgen_futures:spawn_localという同期的に実行する方法があるみたい。
#[function_component]
fn HelloWorld(props: &Props) -> Html {
let response = use_state(|| ResponseData::default());
let resp = response.clone();
wasm_bindgen_futures::spawn_local(async move {
let fetchedResponse: ResponseData =
Request::get("https://api.github.com/repos/rustwasm/wasm-bindgen/branches/master")
.header("Content-Type", "application/json")
.send()
.await
.unwrap()
.json()
.await
.unwrap();
resp.set(fetchedResponse);
});
html! { <>{"Am I loading? - "}{*response}</> }
}
最後の行で所有権関係のエラーが出てしまう・・・
フロントエンドってマルチスレッドのプログラムほぼ書かないから、スレッドセーフに書ける仕組みってもしかしていらない・・・?
move{}
これを理解していないで使ってるのが悪い気がしてきた
これはこのなかで使ってる変数に全部借用させるみたいな感じ?
cannot move out of dereference of UseStateHandle<ResponseData>
move occurs because value has type std::string::String
, which does not implement the Copy
traitrustcClick for full compiler diagnostic
main.rs(48, 36): value moved due to this method call
mod.rs(449, 13): into
takes ownership of the receiver self
, which moves value
main.rs(48, 36): you can clone
the value and consume it, but this might not be your desired behavior: <std::string::String as Clone>::clone(&
, )
それにしてもこのエラーがなくならない・・・
いったんyewはあきらめ
Elmを試してみる。とりえあずイントロダクションをやる
イントロダクションした感想
最近ちょっと話題に上がったテキストでのプログラミングが最善じゃない説。その裏付けに使えそうだなと思った。
gitに管理しやすいようにフォーマットしたり、改行を多くしたり、テキストの弊害というよりGitの弊害ではあるけど、そういうとこすごく感じた。
次はデータフェッチをやってみる
TsっぽくTypesディレクトリを作ろうとしていろいろしたら怒られた
こういうことらしい
要するにファイルを極端に短く分けてはいけないみたい。自分は結構追加する機能をファイルのどこに追加するのか迷うので、tsの1関数1ファイルみたいなスタンスはかなり楽に開発できたのだが、それを一つにまとめるメリットはどこにあるんだろうか
なんでもかんでも外部に公開せずに済むってのはありそう
参考としてちょうどいいものだということでちょっと見てみた
tsであったutilsやtypes、libs、componentsなどがなくあくまで関心ごとに分かれている感じ好きかも
普通にコード書いてると100行超えることはまぁまぁあって、elmはgitのせいで改行が多いにしても400行ぐらいが許容値かなぁ・・・
それ以上増えるどのファイルに何の機能があるのかわかりづらくなる気がする。
たぶんそれをコメントで書けって話だと思うんだけど、それファイルで分割したらlsで見れるわけだし、ファイルエクスプローラーで階層がある状態で分かりやすくなるわけだし・・・
1000行行くってのはなかなか怖いなぁ
Rustみたいにファイル外にどれぐらい広く公開するかを指定しなくていいけど、公開しすぎないってシステムなのか
いまいちCmdとSubがよくわからない
コミュニティは怖そうだしあんま自分向きな言語じゃない気がするなぁ
まわりにElmerが欲しい気持ちになる
どこまでが副作用でどこまでが主作用なのかの理解が怪しい
Msgを受け取ってHTMLを返す、以外の動作すべてが副作用って理解であってそう?
もともと異なる概念として扱われていたものを、Elmだと同じような文法で表すのが認知負荷が高くてつらい。
本質的には同じ概念だから、分ける必要がないということなんだろうか
Elmのチュートリアルはまじでやらないほうがいいって言われてたけど、とりあえずTutorialやってみよう
と思ったけど途中で401で見られない・・・なんだこの環境は・・・
いったんパス
次はScala.jsのためにScalaへ入門してみる。
このツイート見てコンポーネントベースでの開発ができるのならと思ったのがきっかけ。
とりあえず入門を参考に環境構築する
csを使った環境構築でjavaも入れてくれるみたいな感じだったけど、自分の環境だとうまくいかなかったのでOpenJDK8を手動で入れる。
とりあえずツアーやる。
しばらくScalaの好きなところ発表ドラゴンになる
クラスはサブクラス化と、多重継承を巧みに置き換える柔軟なミックスインを基にした合成 機構により拡張されます。
多重継承ないの好き好き大好き~
まず式と値をベースにして教えてくれるの、関数型慣れてない人にやさしくて好き好き大好き~
valとvarがあるの好きかもしれん。見分けづらいけど、あんまりvalかvarなのか意識して変数宣言見ることないし、おなじ変数という概念なら似たような見た目の方が認知負荷も低くて好き好き大好き~
{}で囲むことで式をまとめることができます。これをブロックと呼びます。
ブロックの最後の式の結果はブロック全体の結果にもなります。
関数
全体的に文法がTypeScriptに似てるの好き好き大好き~
最後に式おくと結果になるのRustみたい(というか関数型言語全般?)で好き好き大好き~
関数がtsでいう無名関数をベースにしてるの、あっちは後からアロー関数が来た都合でちょっと普通の関数とアロー関数の書き方がだいぶ違うように見えたから、Scalaみたいに無名関数を変数に入れるのが名前付き関数として処理するのめっちゃ好き好き大好き~
あと無名関数自体がアロー関数とほぼ書き方一緒なのも好き好き大好き~
メソッド
こっちが従来の名前付き関数っぽいが、相変わらず文法がTypeScriptチックで好き。自分は関数の返り値は->
とか=>
より:
の方が好きなタイプ
def name: String = System.getProperty("user.name")
パラメータリストをとらないときにかっこを省略するの、Elmの定数感あっておもろい。この方が短く記述できるってだけかな?
複数のパラメータリストを受け取れるの、これもやっぱりElmみたいな引数を指定した状態の関数を別の関数として呼び出すのとかに使うのかな?
クラス
greet メソッドの戻り値の型はUnitです。Unitは戻り値として意味がないことを示します。 それはJavaやC言語のvoidと似たような使われ方をします。(voidとの違いは、全てのScalaの式は値を持つ必要があるため、 実はUnit型のシングルトンで()と書かれる値があります。その値には情報はありません。)
こういう一般的な言語との違いをメリットとともに書いてくれると「なんでvoidじゃないんだよ!」っていう別言語をやるときのストレスが減るから好き好き大好き~
あとプロパティ値を持つんじゃなくて引数みたいな形で扱うことによって、コンストラクタ内で値をいじりまくったり、メソッドの中で変更したりってのがないから状態がなくていいな。ついでにコンストラクタも自動でできるのも好き(tsだとないから)
ケースクラス
これが個人的に神。
tsのtype aliasみたいに手軽にデータ構造だけを表しいときに使えるのがすごい好き。
newがない状態でインスタンス化されるってことは、スタックに置かれるってことだったりするのかな?
比較する際も値で比較されるみたいだし、タプル感あるなぁ
オブジェクト
僕の今までいた場所だとインスタンス化されたクラスってニュアンスだったが、ここでは違うみたい。
あくまで、シングルトン用のもので、状態も持っている。シングルトンはよくないってどこかで聞いたことあるけどなんでか忘れてしまった…
イメージとしてはstatic classって感じなんだけど、どう違うんだろう
トレイト
多重継承問題の救世主。
Rustで慣れてるからとりあえず同じようなものだと思っておこう。
nullがいてちょっとビビったけど、あくまでJVM互換性のために残されてるみたい。良き。
ユーザーが定義する方は全部AnyRefのサブクラスになるみたい。
クラス
デフォルト引数持てるの好き好き大好き~
コンストラクタ
名前指定して引数指定できるの好き好き大好き~
プライベートメンバーとゲッターセッター構文
あ、あくまでメンバがないってわけではないのか。ふつう作れるみたい。
でデフォルトだとpublic、ここら辺は従来のオブジェクト指向って感じで、ちゃんとJVMで動くためのJava互換って感じかな。
C構文っていうぐらいだから専用の文法(C#みたいなの)があるのかなぁって思ったけど、そうでもなさそう・・・?
トレイトの定義
trait Iterator[A] {
def hasNext: Boolean
def next(): A
}
型ジェネリクスが<>じゃなくて[]なのちょっと面白いかも。どういう意図があったんだろう。
for ((a, b) <- numPairs) {
println(a * b)
}
for内包表記もなかなか既存の言語と似ていて好き好き大好き~
英語の文法に合わせる系のアプローチとられると自然とSVO系の言語になるからSOVの言語を話している私としてはなるべくやめてほしいんだよなぁ
ちょっと多重継承感あるけど大丈夫なのかな
とはいえ、今まではクラスのみによる現実世界のオブジェクトにだけ注目していたシステムでやりくりしていたのに比べて、「能力」に注目したトレイトってい概念でだいぶすっきりしそうってのはわかる。
ここら辺に関しては本当にTypeScript(JavaScript)に近くてとても好き好き大好き~
やっぱりElmでやった概念とかなり近かった。ただそれだけじゃない利点がありそうなんだけどとりあえずスキップ。
これが
numbers.foldLeft(0, (m: Int, n: Int) => m + n)
これになる理由がよくわからん
numbers.foldLeft(0)(_ + _)
一番欲しかったやつ。Typescriptからの移行先を探してる理由は大体これ。
caseのあとにifでさらに絞り込めるの自由度が高そうで面白そうなんだけど、ちゃんと全部のアームが網羅できてないときにエラー出してくれるのかな・・・?
def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = {
notification match {
case Email(sender, _, _) if importantPeopleInfo.contains(sender) =>
"You got an email from special someone!"
case SMS(number, _) if importantPeopleInfo.contains(number) =>
"You got an SMS from special someone!"
case other =>
showNotification(other) // 特別なものではなく、オリジナルのshowNotification関数に委譲します。
}
}
型のみでのマッチング
これ、かなり面白そうだけど、特定のクラスが継承されているクラス一覧をコンパイラが認識してないと、match文で全部網羅しているかどうか判定できないと思うんだけど、どうなってるんだろう
コンパニオンオブジェクト
これ好きかも。staticメソッドや、staticメンバ変数を別の場所にまとめて書くことによって視認性が上がる気がする。
クラスメソッドより、オブジェクトにあるメソッドの方がthisがない分テストしやすいから、こういう部分が書きやすくなってるのかなって思った。
正規表現がリテラルとか文法レベルでサポートされてるわけじゃないけど、簡単に正規表現にできるようになってるの好き好き大好き~
正規表現だけにリテラル用意してあげるのやりすぎでは…って思ってた。
applyとunapplyちょっとよくわからなかったからbing君に聞いてみた
scalaのapplyとかunapplyってほかの言語だとどういう概念だったりする?僕はC,C++,Rust,Typescript,Pythonあたりができるからそこら辺の言語を使って説明してくれると助かる。
Scalaのapplyとunapplyは、他の言語では直接的な対応関係はありませんが、それぞれの概念を理解するために、あなたが知っている言語を使って説明します。
apply: Scalaでは、applyメソッドはオブジェクトが関数のように呼び出されたときに実行されます。Pythonでの__call__メソッドやJavaScriptでのcallメソッドに似ています。例えば、Scalaでは以下のように書くことができます
unapply: Scalaでは、unapplyメソッドはパターンマッチングで使用されます。これは他の言語には直接的な対応関係はありませんが、Rustのmatch文やPythonのif/elif/else文に似た概念と考えることができます。unapplyはオブジェクトを何らかの値に「分解」します。例えば、Scalaでは以下のように書くことができます:
超わかりやすい。ここまで知らない言語を分かりやすく説明されると、ほんと自然言語LLMが認識できない言語の習得外大変になるなぁという気持ちになった。
とりえあず、applyは
applyメソッドはオブジェクトが関数のように呼び出されたときに実行されます。
とのこと、とても分かりやすい
で、unapplyは簡単に言うとRustで値を持つEnumをmatchしたときに中の値を取り出す時の挙動を自分で定義できるみたい。
for内包表記
ifが式になってるみたいに、forの式版みたいなイメージ
Pythonにいて好きだったんだけど、こっちの方がforの式版って感じがする文法で好き好き大好き~
for内包表記を使ってテスト書くときにrange関数結構便利だったんだけど、scalaにもあるのかな
Nilは空配列のこと、mapとかfor inをちゃんと使えば問題ないと思うけど、確かにからかどうかをプログラム内で判定することって多いから、それを型で判定できるのは面白そう。
ジェネリクスに[]
を使うの、いままでは<>から型の匂いを感じてたんだけど、それができなくなるのがちょっと不安、でも正直インデックスアクセスするより型ジェネリクス使う方が多い気がするから押しやすい記号をそこに割り当てるのはセンスあるかも
初めての概念出てきた気がする。ちょっと気を引き締めよう。
共変とか反変とか非変ってのは要するに親クラスを継承した子クラスを許すかどうかってこと見たい。
class Foo[+A]
の場合はAを継承したクラスを許すが、class Foo[A]
の場合は許さない
class Foo[A]
の場合はAの親クラスを許すが、class Foo[-A]
の場合は許さない
Aの親クラス〇、Aの子クラス〇 [+A]
Aの親クラス〇、Aの子クラス× [A]
Aの親クラス×、Aの子クラス× [-A]
Aの親クラス×、Aの子クラス〇 [+A]
Aの親クラス×、Aの子クラス× [A]
Aの親クラス〇、Aの子クラス× [-A]
[追記]
めるくんさんにえぐい長文の解説をしていただきました。
今回は[A]にたいしてジェネリクス引数を渡す時の特徴にしか言及していない状態でしたが、どうやら代入するときの暗黙的な型変換を許すかどうかっていうのに影響数るみたいです。
Aの場合は許さないけど、+Aなら親クラスを勝手にアッパーキャストしてくれたりというのがあるようです。
今度はtsのextendsのScala版って感じ。Tsは型の構造を見てガードするシステムを撮ってるけど、こっちはクラスの名前や、その継承元を見て判断するので、さっきの+Aみたいなのがextendsでも必要になる。
上限型境界: こいつの子である
下限型境界: こいつの親である
クラスの中にクラスあるの、ツリー構造とノードの例しか思いつかないけど、それに関してはかなりいいかも。scalaで自作パーサー作ってみたいなぁ
TypeScriptユーザー的には型を手軽に混ぜられるのはかなりうれしい。
trait User {
def username: String
}
trait Tweeter {
this: User => // thisが再割り当てされます
def tweet(tweetText: String) = println(s"$username: $tweetText")
}
Tweeter.scalaでUser.scalaをimportしなくていいって話だと思うんだけど、importしなくていいってのがどれぐらいうれしいのかよくわからないし、this:Userってやってる時点でimport必要なのでは?
文字だらけでつらい~もうちょっと図とかコードで説明してくれないと自分のIQだと理解できない~()
bingに聞いたところ簡単に言うと、ここで紹介されているimplicit methodはメソッド名が大事、scalaはキャストするときとか暗黙的に関数を呼び出している場合があり、それをオーバーライドすることができるってことみたい
あ、これ違う、この内容は次の章で紹介されてる内容っぽいんだけどそもそもscala3だとないみたいだからいったん放置する
def listOfDuplicates[A](x: A, length: Int): List[A] = { // 引数に型パラメータがある場合に限り
if (length < 1)
Nil
else
x :: listOfDuplicates(x, length - 1)
}
println(listOfDuplicates[Int](3, 4))
println(listOfDuplicates("La", 8)) // 型パラメータを省略することができる
こういう書き方ができるのちゃんと関数型って感じがして好き好き大好き~
10.+(1)
明らかに人類にはまだ使いこなせないレベルのわかりづらさで笑った。さすがにここまで優先順位任せのコードは書かないだろうけど、あんまりこういうことしない方がよさそうやな。
def whileLoop(condition: => Boolean)(body: => Unit): Unit =
if (condition) {
body
whileLoop(condition)(body)
}
var i = 2
whileLoop (i > 0) {
println(i)
i -= 1
} // prints 2 1
なんだこの書き方{}をかっこなしで渡してる
whileLoop (i > 0) ({
println(i)
i -= 1
})
こうじゃなくてもいけるんかな
tour of scalaはscala2の内容だからscala3との相違とか知りたいけど、その前にreactライクに書けるライブラリがscala3に対応しているか調べておこう
書けそうなので、いい感じの2to3のサイトとかないかな
日本語だと嬉しい
げ、scala3ってインデントベースの構文取り入れてるのか・・・
インデントベースって要するにインデント周りを手動でフォーマットする必要があるわけで、何がしたいのかよくわからん。
必ずインデントが必要っていうけどフォーマッター通すだけでできるんだからいらないやん・・・
後ちょくちょく出てくるDottyってなんや
これとかわかりやすかった
Dottyってのはscala3用のコンパイラをはじめとした処理系のことを指す。
じゃあScala2の処理系には別で名前とかあったりするんかな
よし、いよいよ本題のscala.jsをやっていこう
やってみたけど、2024/5現在、一切躓かないでできた。
日本語コミュニティのレスポンスも複数人からもらえたり、ドキュメントが安定していたり、優秀な言語というのはわかるのだが、それ以前に集まっている環境や人のクオリティがかなり高いなぁと
object Main:
def appElement(): Element =
div(
a(href := "https://vitejs.dev", target := "_blank",
img(src := "/vite.svg", className := "logo", alt := "Vite logo"),
),
a(href := "https://developer.mozilla.org/en-US/docs/Web/JavaScript", target := "_blank",
img(src := javascriptLogo, className := "logo vanilla", alt := "JavaScript logo"),
),
h1("Hello Laminar!"),
div(className := "card",
button(tpe := "button"),
),
p(className := "read-the-docs",
"Click on the Vite logo to learn more",
),
)
end appElement
end Main
end 〇〇とかいう謎の構文出てきた
endマーカーというらしい、どこまでがブロックなのかわかりづらいので、見やすさのために置いていいらしい
括弧でくくっちゃダメなんですかね・・・
そもそもインデントベースって誰が書いても同じようなコードになるのも利点じゃなかったっけ?
あとからインデントベースを追加したのがかなり謎だな・・・
読んでみたけど、なんかあんま受け入れられた理由がわからんな・・・
課題は残ってメリットは不明瞭で、とりあえず実験で入れてみました!までしかわからなくて、実験した結果〇〇だったので、Scala3として導入されました!がわからんかった
総合的にこの時代、このタイミングでv3としてこういった文法を入れちゃう開発陣が相当挑戦的、かなり実験的な言語なのかな?という印象を受けるため、何かを開発する言語としては向いていない気がする
まて、そう考えるのは早計かもしれない。今までインデントベースの弱点だと私が考えていた「コピペのしずらさ」が、今のscalaはフォーマッターによって自動的に括弧ベースをインデントベースに書き換えてくれるわけで、つまり括弧ベースの書きやすさとインデントベースのシンプルさが組み合わさった状態なんじゃないのかこれ。
インデントベースはブロックが大きいとどのブロックに属してるのかがわかりづらくなる問題があるけど、これはindent-rainbowを使えば問題なし
要するにプログラマが入力したものをそのまま読むのがおかしい話で、プログラマが入力したものを読みやすい形式に変換して、プログラムを読むときはそれを読むってするある意味今までなかったアプローチに偶然にしろなっているんじゃないだろうか
よくよく調べてみたら例外の処理がtry catchだったので個人的にはいまいちかも…
Moonbit
ゴールデンウイーク中にこの記事終わらせたかったけど、思ったよりRust,Scala,Elmがよくなかったので延長戦。
moonbitでのフロントエンド開発に挑戦してみる。
簡単に言うと所有権システムがないRustって感じで、マルチスレッドプログラミングすることが少ないであろうフロントエンドに一番向いている言語だと思う
let b =
#| Hello
#| MoonBit
#|
この複数行でリテラルかけるの好き好き大好き~
Rustと違ってtraitとは別に単体でメソッドを作れるみたい
手軽にできて面白そう
というよりこれはimplの代わりだな
pipeがあるの関数型大好き人間にはかなりいいんじゃ名だろうか、自分はメソッドチェーンの方が好きだからあんまり使わないけど
moon new
でカレントディレクトリに作るか別のディレクトリに作るか、デフォルトだと別ディレクトリになるとかいろいろ好きすぎる
rustは標準ライブラリとランタイムでテストがかけたけど、moonbitはさらに文法から実装されている。
良すぎる
test "hello" {
if hello() != "Hello, world!" {
return Err("hello() != \"Hello, world!\"")
}
}
mainとかlibってのは特殊な名前なのかな?それともに任意でつけられる名前ってことなのかな
examplesにcanvasをいじくってるのは見当たるんだけど、DOM操作してるのが見当たらない。というかそもそもwasmってDOM操作とかしない?
一応この記事はReactっぽく書ける言語を探すってのが趣旨だから、それを考えるとさすがになしかな・・・
自分がReactを作り切れるほどの技術もなければ、この言語ですべてが解決する確信があるわけでもないわけで悲しいかな
いったんmoonbitは置いておいて、Scalaのコミュニティの良さとインデントベースなどの誤解、例外処理の誤解もろもろが解消したのでもう一度Scala.jsをやってみようと思う。
本当に移行する必要があるのか?
というかそもそもTypeScriptから本当に移行するべきなのか、比較してみようと思う
TypeScriptの不満点
- tsconfigでいろいろ弄る必要がある。(特にファイル回り)
- 意図しない例外のキャッチ漏れが発生する
- ts-resultsをつかっても標準APIやその他ライブラリが例外を投げる前提で作られているので、いちいち変換する必要がある
- Rustみたいに?みたいな文法がないと例外をキャッチしないで上に飛ばす時にめんどくさすぎる
- 例外をキャッチしてもunknownで来るから、キャッチしたい例外とそれ以外を判定するのが大変
Rust
- 〇 tsconfigほど弄る箇所は少なくてすむ
- 〇 Result型によって意図しない例外キャッチ漏れは発生しない
- 〇 Resultをキャッチしない場合でも文法
?
で楽に上に持っていける
- 〇 Resultをキャッチしない場合でも文法
- △ 複数種類の例外を返す時はそれ用のEnumが必要になる(Unionはunsafeらしい)
Elm
- 〇 tsconfigほど弄る箇所は少なくてすむ
- 〇 Result型によって意図しない例外キャッチ漏れは発生しない
- × Rustのような?はない
- 〇複数種類の例外を返す時はユニオン型をつかってかえされたほうでもパターンマッチングでの絞り込みができる
Scala
- 〇 tsconfigほど弄る箇所は少なくてすむ
- △ Either型があるが、そのほかの例外システムがあるからどっちみちTypeScript+ts-results型みたいになる?
- 〇少なくともゆるき良き?throw-catchはあるので、上位の関数への伝播は楽そう
- ×そもそも例外は型安全じゃないので終わってる
- △Eitherの場合でもそれ用の親クラスが必要になる
moonbit
- 〇 tsconfigほど弄る箇所は少なくてすむ
- 〇 Result型によって意図しない例外キャッチ漏れは発生しない
- 〇 Resultをキャッチしない場合でも文法
?
で楽に上に持っていける
- 〇 Resultをキャッチしない場合でも文法
- △ 複数種類の例外を返す時はそれ用のEnumが必要になる
ただしScalaの例外の部分などはもう少し検証する必要がありそう。
試しにSlinkyでデータフェッチする例を書いてみようと思う。
slinkyはプロジェクトテンプレートが用意されているのでとても好き
sbt new shadaj/create-react-scala-app.g8
でプロジェクト作成
公式リポジトリのhttps://github.com/shadaj/slinky/tree/main/docs/src/main/scala/slinky
にexampleがたくさんあるので読み漁る
データフェッチングのやり方がどうなるのかわからない。
wasmとかになるならまだしもjsになるわけでその場合は好き勝手にScalaのフェッチングライブラリ使うわけにはいかないだろうし・・・
nomadBlackyさん( この 記事書いた人 )のコード( https://github.com/NomadBlacky/mmsf-hub/blob/master/frontend/src/main/scala/mmsf_hub/Main.scala )を読むとXMLHttpRequestを使っている部分がある。
こんな感じでjsのものを使うことができるんだ・・・
それ以前にorg.scala.domという知らない概念が出てきたので調べてみる。
どうやらDOM周りの操作をできるライブラリみたい。
というかXMLHttpRequestってDOM周りのことだったんだ。知らなかった。
Fetchの例もあった。
てかFetchもDOM周りだったんだ・・・
def main(pre: html.Pre) = {
// import
import scala.concurrent
.ExecutionContext
.Implicits
.global
import js.Thenable.Implicits._
// create url
val url =
"https://www.boredapi.com/api/activity"
// main
val responseText = for {
response <- dom.fetch(url)
text <- response.text()
} yield {
text
}
// 帰ってきたtextsをコンテンツに追加
for (text <- responseText)
pre.textContent = text
}
for {} yield {} とかいう知らない構文出てきた