Elixir:関数型言語ではOOPはどのように見えますか?

最近、PLOへの別れのトピックに関する記事と議論、およびこの概念にアランケイが最初に入れた意味の検索がより頻繁になりました。



逃した人に対するケイのいくつかのことわざ
「オブジェクト指向」という用語を作成しましたが、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 :





— pid, API-. start_link, , , ( ) new.



- system-wide , , . pid API-, .. .



Elixir , .



P.S. , Elixir , , — . « ».



All Articles