Elixir、Rust、javascriptの構文の比較
Elixir、Rust、javascriptの構文の比較
標準出力に出力
反復処理
for
while
制御構文
if
パターンマッチ
リスト(と配列)
リスト内包
マップ
構造体(とオブジェクト)
文字列
結合
関数
無名関数
イテレーション処理、高階関数
try、エラー処理
日付
用語
Box
イテレーション処理と高階関数
標準出力に出力
大体
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型にすることにより異なるデータを保持することができる。(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を生成することで、値はヒープに保存される。またスタックにはヒープへのポインタが保存される。
Vecはコンパイル時に格納するデータ型(VecのTのこと)が決まっている必要がある。
よってVecがBox型を格納することで、異なるデータ型の要素を格納することができる。
イテレーション処理と高階関数
大体はイテレーション処理 ≒ 高階関数
2つは以下の点で異なる。
-
- イテレーション処理はforを使用して一要素ごとに処理を行うイメージ。
- 高階関数は関数ごとにすべての要素を処理するイメージ。