逃した人に対するケイのいくつかのことわざ
「オブジェクト指向」という用語を作成しましたが、C ++を念頭に置いていなかったことがわかります。
私にとってのOOPとは、メッセージング、ローカルの保持と状態プロセスの保護と隠蔽、およびすべてのものの極端な遅延バインディングのみを意味します。
私がずっと前にこのトピックの「オブジェクト」という用語を作ったのは残念です。なぜなら、多くの人々がより小さなアイデアに集中するようになるからです。 大きなアイデアは「メッセージング」です。
優れた成長可能なシステムを作成するための鍵は、内部のプロパティや動作がどうあるべきかではなく、モジュールの通信方法を設計することです。
レイトバインディングにより、プロジェクト開発の後半で学習したアイデアを、従来のアーリーバインディングシステム(C、C ++、Javaなど)よりも指数関数的に少ない労力でプロジェクトに再定式化できます。
私は型には反対ではありませんが、完全な苦痛ではない型システムについては知りません。そのため、動的型付けがまだ好きです。
これらの議論に関連して、Erlang / Elixirがケイが「オブジェクト指向」の概念に設定した基準を非常によく満たすという考えがしばしば生じます。 しかし、誰もがこれらの言語に精通しているわけではないため、一般的なC ++、Java、C#よりも機能言語がオブジェクト指向になりやすいという誤解があります。
この記事では、 exercism.ioを使用した簡単な例で、OOPがElixirでどのように見えるかを示したいと思います。
タスクの説明
学生の名前を保存する小さなプログラムを作成し、学習するクラス番号でグループ化します。
最終的に、次のことができるはずです。
最終的に、次のことができるはずです。
- クラスに学生名を追加
- クラスの全生徒のリストを取得する
- すべての成績のすべての学生のソートされたリストを取得します。 クラスは昇順(1、2、3など)で並べ替える必要があり、生徒の名前はアルファベット順に並べ替える必要があります。
テストから始めて、関数を呼び出すコードがどのようになるかを確認します。 ExercismがRuby用に準備したテストを見てください。OOPでは、演算子でさえ他の人のメソッドであることがわかりました。
そして、このプログラムのElixirバージョンについても同様のテストを作成します。
Code.load_file("school.exs") ExUnit.start defmodule SchoolTest do use ExUnit.Case, async: true import School, only: [add_student: 3, students_by_grade: 1, students_by_grade: 2] test "get students in a non existant grade" do school = School.new assert [] == school |> students_by_grade(5) end test "add student" do school = School.new school |> add_student("Aimee", 2) assert ["Aimee"] == school |> students_by_grade(2) end test "add students to different grades" do school = School.new school |> add_student("Aimee", 3) school |> add_student("Beemee", 7) assert ["Aimee"] == school |> students_by_grade(3) assert ["Beemee"] == school |> students_by_grade(7) end test "grade with multiple students" do school = School.new grade = 6 students = ~w(Aimee Beemee Ceemee) students |> Enum.each(fn(student) -> school |> add_student(student, grade) end) assert students == school |> students_by_grade(grade) end test "grade with multiple students sorts correctly" do school = School.new grade = 6 students = ~w(Beemee Aimee Ceemee) students |> Enum.each(fn(student) -> school |> add_student(student, grade) end) assert Enum.sort(students) == school |> students_by_grade(grade) end test "empty students by grade" do school = School.new assert [] == school |> students_by_grade end test "students_by_grade with one grade" do school = School.new grade = 6 students = ~w(Beemee Aimee Ceemee) students |> Enum.each(fn(student) -> school |> add_student(student, grade) end) assert [[grade: 6, students: Enum.sort(students)]] == school |> students_by_grade end test "students_by_grade with different grades" do school = School.new everyone |> Enum.each(fn([grade: grade, students: students]) -> students |> Enum.each(fn(student) -> school |> add_student(student, grade) end) end) assert everyone_sorted == school |> students_by_grade end defp everyone do [ [ grade: 3, students: ~w(Deemee Eeemee) ], [ grade: 1, students: ~w(Effmee Geemee) ], [ grade: 2, students: ~w(Aimee Beemee Ceemee) ] ] end defp everyone_sorted do [ [ grade: 1, students: ~w(Effmee Geemee) ], [ grade: 2, students: ~w(Aimee Beemee Ceemee) ], [ grade: 3, students: ~w(Deemee Eeemee) ] ] end end
, «» «» School:
school = School.new school |> add_student("Aimee", 2) # => :ok school |> students_by_grade(2) # => ["Aimee"] school |> students_by_grade # => [[grade: 2, students: ["Aimee"]]]
, , , , . , . -> pipe- |>.
, , , pipe- , , . Elixir :
school = School.new School.add_student(school, "Aimee", 2) # => :ok School.students_by_grade(school, 2) # => ["Aimee"] School.students_by_grade(school) # => [[grade: 2, students: ["Aimee"]]]
! «» . . , …
, , Erlang, Elixir, OTP, - , , Erlang. OTP ( ). , — GenServer. ( Erlang).
- , , . , , race condition , . — GenServer, — .
, , handle_call (c ) handle_cast ( ). , . .. API-, , .
, , (.. ).
, . , :
defmodule School do use GenServer # API @doc """ Start School process. """ def new do {:ok, pid} = GenServer.start_link(__MODULE__, %{}) pid end @doc """ Add a student to a particular grade in school. """ def add_student(pid, name, grade) do GenServer.cast(pid, {:add, name, grade}) end @doc """ Return the names of the students in a particular grade. """ def students_by_grade(pid, grade) do GenServer.call(pid, {:students_by_grade, grade}) end @doc """ Return the names of the all students separated by grade. """ def students_by_grade(pid) do GenServer.call(pid, :all_students) end # Callbacks def handle_cast({:add, name, grade}, state) do state = Map.update(state, grade, [name], &([name|&1])) {:noreply, state} end def handle_call({:students_by_grade, grade}, _from, state) do students = Map.get(state, grade, []) |> Enum.sort {:reply, students, state} end def handle_call(:all_students, _from, state) do all_students = state |> Map.keys |> Enum.map(fn(grade) -> [grade: grade, students: get_students_by_grade(state, grade)] end) {:reply, all_students, state} end # Private functions defp get_students_by_grade(state, grade) do Map.get(state, grade, []) |> Enum.sort end end
, , GenServer, 3 :
- API — , GenServer , / ..
- Callbacks — , GenServer: ..
- Private functions — ,
— pid, API-. start_link, , , ( ) new.
- system-wide , , . pid API-, .. .
Elixir , .
P.S. , Elixir , , — . « ».