ReScript とクラスレス コーディング


ReScript は今、私のお気に入りの言語の 1 つです. JavaScript にトランスパイルする、理解しやすい関数型言語です.他のいくつかのトランスパイルから js への言語とは異なり、開始するために jvm/.net ランタイムは必要ありません.実際、typescript よりも高速に実行できます...

https://rescript-lang.org/docs/manual/latest/installation

その構文は本質的に、機能要素が慎重に作成された JavaScript のサブセットです.

使いやすいだけでなく、コードの予測可能性と安全性が大幅に向上します.

ただし、ReScript にはクラス/プロトタイプがなく、必要ありません.

この投稿では、このクラスレス パラダイムで簡単に作業する方法を紹介したいと思います.

関数から状態を分離する



まず、状態の概念を機能から分離するのに役立ちます.

この例では、人物のインスタンスを作成します.これは typelet で行われます.


type person = {
  name: string,
  age: int
}

let bob = {
  name: "Bob",
  age: 28
}



上記では、bob の署名に一致する型があるため、bob の型は person であると推測されます. bob を let bob: person = { ... } で明示的に宣言することもできました.

状態ができたので、関数について考えることができます...

関数をモジュールにグループ化する



同じタイプのデータで機能する関数を共通のモジュールにグループ化するのが一般的です.これは、クラス内のメソッドに多少似ています.以下のモジュールには、greettellAge 関数があります.

繰り返しますが、thisPersonperson 型であることを関数に伝える必要がないことに注意してください.関数はこれを推測できるからです.


module Person = {
  let greet = thisPerson => {
    thisPerson.name
      ->x => { x ++ " says Hello." }
      ->Js.log
    thisPerson
  }

  let tellAge = (thisPerson) => {
    open Belt.Int
    thisPerson
      ->x => { x.name ++ " is " ++ x.age->toString ++ " years old" }
      ->Js.log
    thisPerson
  }

}



ReScript では、前の値を次の関数に「パイプ」できる -> 演算子がよく見られます.たとえば、 10->increment->incrementincrement(increment(10)) と同じです.

ReScript は return キーワードを使用しませんが、関数の最後の式を返します.どちらの関数も thisPerson を返します.

それを一緒に入れて



それでは、bob を関数の 1 つに「パイプ」します... greet としましょう.


// Note: In ReScript, a top-level expression must always be `unit`. 
// `unit` is very similar to `undefined` is javascript.
// Since `bob->Person.greet` returns a type of `person` we use `ignore` to ignore this type and just return `unit` instead.

bob->Person.greet->ignore 


Person.greetbob を返すので、他の Person 関数にパイプを続けることができます...


// Using open allows us to drop the need to write `Person.greet` and `Person.tellAge` and just use `greet` and `tellAge`

open Person 

bob
  ->greet
  ->tellAge
  ->ignore


-> を OOP のメソッドチェーンに少し似た方法で使用できることに注意してください.

このスタイルの構文の優れた点の 1 つは、bob モジュールから関数に Person をパイプする必要がなく、実際、署名を受け入れる任意の関数にパイプできることです.

たとえば、incrementAge というスタンドアロン関数を作成してみましょう ...


let incrementAge = thisPerson => {
  name: thisPerson.name,
  age: thisPerson.age + 1
}

open Person

bob->incrementAge->greet->tellAge->ignore



プログラムを実行すると、次のように出力されます.


Bob says Hello.
Bob is 29 years old



不変ファースト


incrementAgebob を変異させたのではなく、パイプを通過し続けるために bob の新しいバージョンを不変に生成したことに気付いたかもしれません.これは関数型プログラミングの重要な部分を示しています.可能な限り、既存の値を変更しないこのような純粋な関数を使用することが最善のアプローチです.たとえば、現在のバージョンの bobbob1YearFromNow を保持できます ...


let bob1YearFromNow = bob->incrementAge

bob->greet->tellAge->ignore

bob1YearFromNow->greet->tellAge->ignore



変化する小道具



コードの 90% 以上は不変でなければなりませんが、クラスをエミュレートして一部の props を変更したい場合はどうでしょう!私たちはそのようにすることができます...

まず、person 型は、特定のプロパティが可変であることを明示的に呼び出す必要があります (デフォルトではすべてが不変であるため).そこから、person を受け取り、age プロパティを変更する関数を作成できます.再び thisPerson を返します.これにより、必要に応じて配管を続行できます.


// Updating person to have a mutable age
type person = {
  name: string,
  mutable age: int
}

let jill = {
  name: "Jill",
  age: 26
}

let mutIncrementAge = thisPerson => {
  thisPerson.age = thisPerson.age + 1
  thisPerson
}

jill->mutIncrementAge->ignore



結論



これで、ReScript でクラスのような動作をエミュレートする方法を見てきましたが、突然変異に関しては、上記のような単一の props の突然変異はめったに見られません.突然変異は通常、関数型言語では可能な限り不変に発生します.しかし、それはパート2のように聞こえます.

ReScriptを使用したことがありますか?
クラスレス言語をどのように使用しますか?

皆さんありがとう :)