Elixir、Rust、javascriptの構文の比較
標準出力に出力
大体
ElixirではIO.puts
を使用する。
IO.puts "Hello, World."
Rustではprintln!
マクロを使用する。
println!("Hello, World");
JavaScriptでコンソールに出力したいならconsole.log()
を使用する。
console.log("Hello, World");
反復処理
for
Elixirは要素とイテレータを<-
で区切る。
形式: for 要素 <- イテレータ do ... end
Rustは要素とイテレータをin
で区切る。
形式: for 要素 in イテレータ {}
JavaScriptは要素とイテレータを()
で囲み、in
で区切る。
形式: for (要素 in イテレータ) {}
while
Elixirにはwhile
やloop
は存在しない。
なので再帰関数を使用するとよい。
defmodule Loop do
def while(0) do
:ok
end
def while(num) do
IO.puts "Hello, World!"
while(num - 1)
end
end
Loop.while(10)
Rust
while 条件 {
...
}
loop {
...
break;
}
JavaScript
while (条件) {
}
制御構文
if
Elixirのcond
を使用するとコンパクトに記述できる。
論理積、論理和にand
とor
を使用する
if :a == :a do
...
else
...
end
if :a == :b, do: ..., else: ...
x = :c
cond do
x == :a or x == :c -> ...
x == :b -> ...
true -> ...
end
Rustでは論理積、論理和に&&
と||
を使用する。
if "a" == "a" {
...
} else if "b" == "b" || "c" == "d" {
...
}
Javascriptのifは条件式を()
で囲む。
論理積、論理和に&&
と||
を使用する。
if ("a" === "c") {
...
} else if ("b" === "b" || "c" === "d") {
...
}
"a" === "a" ? ... : ...
パターンマッチ
Elixirではcase
を使用する。
また->
を使用し、条件と処理を区切る。
デフォルトの処理は_
で記述する。
x = :a
case x do
:a -> ...
:b ->
...
_ -> ...
end
Rustではmatch
を使用する。
また=>
を使用し、条件と処理を区切る。
,
で区切る。
デフォルトの処理は_
で記述する。
let x = "a";
match x {
"a" => ...,
"b" => {
...
}
_ => {}
}
JavaScriptではswitch
を使用する。
また条件をcase
で記述する。
デフォルトの処理はdefalut
で記述する。
let x = "a";
switch (x) {
case "a":
...
break;
case "b":
...
break;
default:
...
}
リスト(と配列)
ElixirのListは異なるデータ型を格納することができる。
list = [1, :ok, "hello"]
IO.puts "[0]: #{Enum.at list, 0}, [1]: #{Enum.at list, 1}, [2]: #{Enum.at list, 2}"
# [0]: 1, [1]: ok, [2]: hello
RustのVecは型安全によって異なるデータ型を持つことができない。
ただしstd::any::Any
を使用しBox<dyn Any>
型にすることにより異なるデータを保持することができる。(Boxについて)
// 実行時に型が決定
let list = vec![1, 2, 3]
use std::any::Any;
let list_with_any: Vec<Box<dyn Any>> = vec![
Box::new(1 as i32),
Box::new("a".to_string()),
Box::new(false)
];
list_with_any
.iter()
.for_each(|item| {
if let Some(integer) = item.downcast_ref::<i32>() {
print!("[0]: {:?}, ", integer)
} else if let Some(string) = item.downcast_ref::<String>() {
print!("[1]: {}, ", string)
} else if let Some(boolean) = item.downcast_ref::<bool>() {
print!("[2]: {:?}", boolean)
}
});
// 配列
let l: [isize; 4] = [1,2,3,4];
println!("{:?}", l);
JavaScriptのArray(配列)は異なるデータ型を格納することができる。
let array = [1, "a", false];
// バッククォート(``)で囲む
console.log(`[0]: ${array[0]}, [1]: ${array[1]}, [2]: ${array[2]}`);
リスト内包
Elixirでは以下のように記述してリスト内包を行う。
形式: 変数 = for 要素名 <- 1..5, do: 値の計算
例 : list = for x <- 1..5, do: x * 2
Rustにはリスト内包が存在しない。
が以下のよう記述することはできる。
let list: Vec<u32> =
(1..5)
.map(|i| i * 2)
.collect();
JavaScriptにはリスト内包がない。
マップ
Elixirでは異なる型のキーと値を保持できる。
2種類の記述があり、どちらかに統一して記述する。
m1 = %{"name" => "Taro", "age" => 34, :adress => "Tokyo"}
# すべてのキーがアトムの場合
m2 = %{email: "aaa@aaa.com", blood_type: "X"}
Rustではstd::collections::HashMap
を使用する。
異なるキーと値の型を保持したいときはBox型を使用する。(Boxについて)
use std::collections::HashMap;
let mut map: HashMap<String, String> = HashMap::new();
map.insert("name".to_string(), "Taro".to_string());
map.insert("age".to_string(), "34".to_string());
println!("{:?}", map);
// 値をBox型にする
let mut map_with_box: HashMap<String, Box<dyn Any>> = HashMap::new();
map_with_box.insert("name".to_string(), Box::new("Taro".to_string()));
map_with_box.insert("age".to_string(), Box::new(34 as u32));
// downcast_refで値を取得
&map_with_box
.iter()
.for_each(|(k, v)| {
if let Some(string) = v.downcast_ref::<String>() {
print!("{}: {} ", k, string);
} else if let Some(integer) = v.downcast_ref::<u32>() {
print!("{}: {:?} ", k, integer);
}
});
JavaScriptではMapクラスを使用する。
let map = new Map();
map.set("name", "Taro");
map.set("age", 34)
構造体(とオブジェクト)
Elixirはフィールドのデータ型を宣言しない。
名前付きマップと比べて、フィールドが欠けていることを防止できる。
defmodule User do
# 初期値を定義している。
defstruct name: nil, age: nil
end
taro = %User{name: "Taro", age: 34}
# 名前付きmap
satoshi = %{name: "Satoshi", age:38}
Rust
struct User {
name: Stirng,
age: u32
}
struct Address(String);
let taro = User { name: "Taro", age: 34 };
JavaScriptでは一般的に定義と生成を同時に行うほうが一般的。
// 定義と生成を同時に行っている
const taro1 = {
name: "Taro",
age: 34
}
// オブジェクトの定義を行っている
const userTemplate = {
name: "",
age: null
}
const taro2 = Object.create(userTemplate)
taro2.name ="Taro"
taro2.age = 34
文字列
結合
Elixirで<>
を使用する。
t = "World."
s = "Hello, " <> t
Rustでは+
を使用する。なお、String
(文字列)と&str
(文字列スライス)の順でないと結合できない。
let t: &str = "World.";
let s = "Hello, ".ti_string() + t;
JavaScriptでは+
で結合する。
let t = "World."
let s = "Hello" + t
関数
JavaScriptでは戻り値をreturnで
無名関数
Elixirではfn関数式を使用するだけ。
形式: 変数 = fn 引数 -> 処理 end
func = fn i -> i * i end
func.(10)
long_func = fn i ->
ans = i * i
IO.puts "result: #{ans}"
end
long_func.(10)
Rustではクロージャ(||
)を使用して無名関数を定義する。
なお型推論が行われるため戻り値の型を記述する必要はない。
形式: let 変数 = |変数: 型| {一行であれば{}は必要ない };
let func = |i: i32| i * i;
func(10);
let long_func = |i: i32| {
let ans = i * i;
println!("result: #{}", ans);
};
long_func(10);
JavaScriptではアロー関数を使用することが一般的(2つ目)
形式: let 変数 = (変数) => {処理}
// function()を用いる
let func = function(i) { return i * i }
func(10)
// arrow()関数を使用して簡略化
// functionを省略できる。
// 引数が一つなら()も省略できる。
let arrow_long_func = i => {
let ans = i * i;
console.log(ans);
}
arrow_long_func(10)
イテレーション処理、高階関数
Elixirでは|>
を使用して関数の結果を別の関数に渡すことができる。
Enum.each()なので注意。
list = [1,2,3,4,5]
list
|> Enum.each(fn i ->
// ここで処理
if rem(i,2) == 0 do
IO.puts i
end
end)
list
|> Enum.filter(fn i -> rem(i, 2) == 0 end)
|> Enum.map(fn i -> IO.puts i end)
Rust
.for_each()なので注意。
let list: Vec<u32> = vec![1,2,3,4,5];
for i in list.iter() {
if i % 2 == 0 {
println!("{}", i);
}
}
list.iter().filter(|&i| i % 2 == 0).for_each(|&i| println!("{}", i));
JavaScript
let list = [1, 2, 3, 4, 5];
for (i in list) {
if (i % 2 === 0) {
console.log(i);
}
}
list
.filter(i => i % 2 === 0)
.forEach(i => console.log(i))
try、エラー処理
Elixirではtry
を使用する。各記述は以下の用途で使用する。
-
rescue
- エラー、特定のエラータイプごとの処理を行う。 -
catch
-throw()
で処理を受け取った値を処理。 -
else
-throw()
で値を受け取らなかったときの処理。 -
after
- try文のあとに行う処理を記述。
Javascriptのcatchとは意味が異なるので注意。
try do
# ここで処理
# throw()で値をcatchに渡すことができる
rescue
エラー -> エラーの処理
catch
# throwで値を受け取ったときの処理
x -> ...
else
# throwで値を受け取らなかったときの処理
_ -> ...
after
# try文のあとに行う処理を記述
end
RustのResult型はエラーハンドリングのための型である。
Rustでは例外が発生するような関数の戻り値はResult型である。
ので、match
で処理する。
x = ... // ここでResult型が戻り値の関数を実行しているものとする。
match x {
Ok(_) => // 処理
Err(_) => // 例外処理
}
JavaScriptのcatchはエラーを処理する
try {
// 処理
} catch (e) {
// エラー処理
} finally {
// 後処理
}
日付
NaiveDateTimeは省略(多用したらよくないらしい)
Elixirではタイムゾーンを指定するためにtzdata
、timex
をインストールする必要がある。
to_day = Date.utc_today()
utc_now = DateTime.utc_now()
tokyo_time = DateTime.now("Asia/Tokyo")
Rustではchronoクレートを使用する。またタイムゾーンを使用するためにchorono_tzを使用する。
use chrono::{Utc, Local};
use chrono_tz::Asia::Tokyo;
let to_day = Utc::now.date_naive();
let utc_now = Utc::now();
// タイムゾーンがAsia/Tokyoであれば、以下2つは同じ
let tokyo_time = Utc.now().with_timezone(&Tokyo);
let local_time = Local::now();
JavaScriptではDateクラスを使用する。
let to_day = new Date();
用語
Box
- Rustにおいて値はスタックに保存される。
-
Box<T>
を生成することで、値はヒープに保存される。またスタックにはヒープへのポインタが保存される。 - Vecはコンパイル時に格納するデータ型(
Vec<T>
のTのこと)が決まっている必要がある。 - よってVecがBox型を格納することで、異なるデータ型の要素を格納することができる。
イテレーション処理と高階関数
大体はイテレーション処理 ≒ 高階関数
2つは以下の点で異なる。
- イテレーション処理はforを使用して一要素ごとに処理を行うイメージ。
- 高階関数は関数ごとにすべての要素を処理するイメージ。