v2.2.0
Melange for X Developers

Melange for X developers

他の言語やプラットフォームに詳しい方は、Melange と他の言語やプラットフォームの比較セクションをご覧ください。

  • JavaScript
  • TypeScript
  • Js_of_ocaml
  • ReScript

For JavaScript developers

Melange は表現力と安全性に重点を置いた、OCaml 上のレイヤー。強力な型システムで JavaScript アプリケーションを安全に構築・保守できるようにする。

Melange では OCaml または Reason 構文を使用してアプリケーションを構築できる。(Reason は OCaml と完全に互換性がある)

Reason では JSX をサポートしていて、ReasonReact のようなバインディングも用意されている。

Cheat sheet

Variable

JavaScriptReason
const x = 5;let x = 5
var x = y;なし
let x = 5; x = x + 1;let x = ref(5); x := x^ + 1;

String / Character

JavaScriptReason
"Hello world!"同じ
'Hello world!'文字列は"を使う必要がある
Character は文字列'a'
"hello " + "world""hello" ++ "world"

Boolean

JavaScriptReason
true, false同じ
!true同じ
||, &&, <=, >=, <, >同じ
a === b, a !== b同じ
深い等号なし(再帰的比較)a == b, a != b
a == b暗黙のキャスティングによる比較はできない

Number

JavaScriptReason
3同じ*
3.1415同じ
3 + 4同じ
3.0 + 4.53.0 +. 4.5
5 % 35 mod 3
  • JavaScript では integer と float の区別はない

Object / Record

JavaScriptReason
静的型なしtype point = { x: int, mutable y: int
{x: 30, y: 20}同じ
point.x同じ
point.y = 30;同じ
{ ...point, x: 30 }同じ

Array

JavaScriptReason
[1, 2, 3][| 1, 2, 3 |]
myArray[1] = 10同じ
[1, "Bob", true]*(1, "Bob', true)
不変リストなし[1, 2, 3]
  • JavaScript の配列は複数のタイプの要素を含むことができるため、タプルは JavaScript の配列でシミュレートすることができる。

Null

JavaScriptReason
null, undefinedNone*
  • OCaml にはnullもそれに起因するバグもない。しかし、実際に必要な場合のために、Option 型 (opens in a new tab)が用意されている。

Function

JavaScriptReason
arg => retval(arg) => retval
function f = function(arg) {}let named = (arg) => ...
const f = function(arg) {}let f = (arg) => ...
add(4, add(5, 6))同じ

Blocks

const myFun = (x, y) => {
  const doubleX = x + x
  const doubleY = y + y
  return doubleX + doubleY
}

Currying

JavaScriptReason
let add = a => b => a + blet add = (a, b) => a + b

JavaScript も OCaml も currying をサポートしているが、OCaml の currying はビルトインで、可能な限り中間関数の割り当てと呼び出しを避けるように最適化されている。

If-else

JavaScriptReason
if (a) { b } else { c }同じ
a ? b : c同じ
switchswitch ただしパターンマッチング

Destructuring

JavaScriptReason
const {a, b} = datalet {a, b} = data
const [a, b] = datalet [| a, b |] = data *
const { a: aa, b: bb } = datalet { a: aa, b: bb } = data
  • この場合、コンパイラーは、データが 2 以外の長さである可能性があるため、すべてのケースが処理されていないと警告する。

Loop

JavaScriptReason
for (let i = 0; i <= 10; i++) {...}for (i in 0 to 10) {...}
for (let i = 10; i >= 0; i--) {...}for (i in 10 downto 0) {...}
while (true) {...}Same

JSX

JavaScriptReason
<Foo bar=1 baz="hi" onClick={bla} />Same
<Foo bar=bar /><Foo bar /> *
<input checked /><input checked=true />
No children spread<Foo>...children</Foo>
  • エレメントを作成する際の引数の洒落に注意

Exception

JavaScriptReason
throw new SomeError(...)raise(SomeError(...))
try {a} catch (Err) {...} finally {...}try (a) { | Err => ...} *
  • finally はない

Blocks

OCaml では、「シーケンス式」は{}で作成され、最後のステートメントで評価される。JavaScript では、これは即座に呼び出される関数式でシミュレートできます(関数本体は独自のローカルスコープを持つため)。

let res = (function () {
  const x = 23
  const y = 34
  return x + y
})()

Comments

JavaScriptReason
/* Comment */同じ
// Line Comment同じ

For TypeScript developers

Melange を使ったアプリケーションの型付けのアプローチは、TypeScript とは多少異なります。TypeScript はその設計目標にあるように、JavaScript との互換性に重点を置いて設計されている。一方、Melange は OCaml をベースに構築されており、表現力と安全性を重視したコンパイラとして知られている。

両者にはいくつかの違いがある。

型推論

TypeScript では引数の型を定義しなければならない:

let sum = (a: number, b: number) => a + b

OCaml では型アノテーションをほとんど使わなくても型を推論することができる。例えば、2 つの数値を加算する関数を次のように定義できる:

let add = (x, y) => x + y;

代数的データ型

TypeScript で OCaml と同じように ADT を構築することはできない。判別可能なユニオン (opens in a new tab)が最も近い類型であり、ts-pattern (opens in a new tab)のようなライブラリがパターンマッチをサポートしていない言語の代替となるだろう。

OCaml では、代数的データ型(ADT) (opens in a new tab)はよく使われる機能である。これによって、小さなブロックから独自の型を構築することができる。そして、パターン・マッチを使えば、このデータに簡単にアクセスできる。

Nominal 型付け

TypeScript では、型付けはすべて構造的である。つまり、同じ実装を持つ 2 つの型の間に境界や分離を設けるのが難しい場合がある。このような場合、タグを使用してノミナル型付けをエミュレートすることができる:

type Email = string & { readonly __tag: unique symbol }
type City = string & { readonly __tag: unique symbol }

OCaml では、ノミナル型が完全にサポートされている。レコードやバリアント (opens in a new tab)のようなコアとなる型はノミナル型である。これは、まったく同じ型を 2 回宣言したとしても、一方の型の値を操作する関数はもう一方の型と互換性がないことを意味する。

OCaml オブジェクト (opens in a new tab)ポリモーフィック・バリアント (opens in a new tab)に使われる構造的型付けもある。

Immutability

TypeScript には、不変性を扱うための 2 つの基本プリミティブがある。constreadonly

最初のプリミティブは、変数の参照が変更されるのを防ぐために使用される。

const a = 1
a = 2 // Error: Cannot assign to 'a' because it is a constant.

二つ目はプロパティをイミュータブルにするために使用する。

type A = {
  readonly x: number
}
 
const a: A = { x: 1 }
a.x = 12 // Error: Cannot assign to 'x' because it is a read-only property.

constreadonlyは参照の変更をブロックするだけで、値については何もしない。const a = [1, 2, 3]readonly x: number[]を使っても、配列の中身を変更することはできる。

OCaml は、リスト、レコード、マップのような不変性を考慮したデータ型を提供している。

厳密性と健全性

TypeScript では、anyのような型や、Functionのような拡張的な型を柔軟に使うことができる。しかし、TypeScript では tsconfig.json ファイルに strict オプションが用意されており、これらの型安全性の低い構造の使用を緩和することができる。一方、OCaml には厳密性を有効/無効にする同様のオプションはない。OCaml では、より厳格な動作を強制するための明示的な設定オプションを必要とせず、言語自体が型安全性を促進する。

TypeScript は、ハンドブック (opens in a new tab)で言及されているように、必要に応じて、実用性のために健全性を犠牲にすることがある。対照的に、OCaml の実装はidentityプリミティブ (opens in a new tab)のような不健全なメソッドを提供しているが、一般的には推奨されておらず、ほとんど使われていない。OCaml コミュニティは健全性を維持することに強い重点を置いており、コードの正しさを保証するために、より安全な代替手段を好んでいる。

Cheat sheet

以下は、TypeScript と OCaml のイディオム間の変換である。OCaml 側では、JavaScript 開発者向けのセクションで述べたように、馴染みやすいように Reason 構文を使っている。

型エイリアス

TypeScriptReason
type Email = string;type email = string;

抽象型

TypeScript:

type Email = string & { readonly __tag: unique symbol }

Reason:

/* in interface `rei` file */
type email;
 
/* in implementation `re` file */
type email = string;

Union 型 / Variants

TypeScript:

type Result = 'Error' | 'Success'
 
type Result =
  | { type: 'Error'; message: string }
  | { type: 'Success'; message: string }

Reason:

type Result =
  | Error
  | Success
 
type result =
  | Error(string)
  | Success(string)

Immutabiliity

TypeScript:

const a = 1
 
type A = { readonly x: number }
type ImmutableA = Readonly
const arr: ReadonlyArray = [1, 2, 3]
type A = { readonly [x: string]: number }

OCaml ではデフォルトで Immutable。

Currying

TypeScript:

type addT = (_: number) => (_: number) => number
const add: addT = l => r => l + r
 
add(5)(3)

OCaml ではデフォルトで有効

Parametric polymorphism

TypeScriptReason
type length = <T>(_: T[]) => number;let length: list('a) => int;