Do you want to pick up from where you left of?
Take me there

Komposisi

Kita tahu dari pengalaman bahawa menyimpan semua fungsi di dalam satu fail dan skop membawa kepada keadaan kelam-kabut. Di dalam pelajaran ini kita akan melihat bagaimana untuk meletakkan beberapa fungsi ke dalam satu kumpulan dan menetapkan sejenis map khas yang dikenali sebagai struct dalam usaha untuk menguruskan kod kita dengan lebih efisien.

Modul

Penggunaan modul-modul adalah cara yang terbaik untuk menguruskan fungsi-fungsi di dalam satu namespace. Tambahan kepada pengumpulan fungsi-fungsi, mereka juga membenarkan kita menetapkan fungsi bernama dan fungsi terlindung yang telah kita lihat dalam pelajaran lepas.

Mari kita lihat satu contoh asas:

defmodule Example do
  def greeting(name) do
    "Hello #{name}."
  end
end

iex> Example.greeting "Sean"
"Hello Sean."

Elixir membolehkan untuk membina modul dalam bentuk bersarang, membenarkan anda untuk mengembangkan penggunaan ‘namespace’ kefungsian anda:

defmodule Example.Greetings do
  def morning(name) do
    "Good morning #{name}."
  end

  def evening(name) do
    "Good night #{name}."
  end
end

iex> Example.Greetings.morning "Sean"
"Good morning Sean."

Ciri-ciri Modul

Ciri-ciri modul adalah yang paling kerap digunakan sebagai pemalar di dalam Elixir. Mari kita lihat satu contoh ringkas:

defmodule Example do
  @greeting "Hello"

  def greeting(name) do
    ~s(#{@greeting} #{name}.)
  end
end

Adalah penting untuk dijelaskan bahawa terdapat beberapa ciri-ciri simpanan dalam Elixir. Tiga yang paling biasa adalah:

Struct

Struct adalah sejenis map khas yang mengandungi satu set key dan value. Ia mesti ditetapkan di dalam satu modul, yang mana ia mendapat namanya(nama struct adalah nama modul yang mana ia terkandung). Menjadi kebiasaan apabila satu modul hanya mengandungi struct di dalamnya.

Gunakan defstruct untuk menetapkan satu struct, di samping satu list katakunci yang mengandungi katakunci dan nilai lalai:

defmodule Example.User do
  defstruct name: "Sean", roles: []
end

Mari kita bina beberapa struct:

iex> %Example.User{}
%Example.User<name: "Sean", roles: [], ...>

iex> %Example.User{name: "Steve"}
%Example.User<name: "Steve", roles: [], ...>

iex> %Example.User{name: "Steve", roles: [:manager]}
%Example.User<name: "Steve", roles: [:manager]>

Kita boleh mengemaskini struct sebagaimana kita mengemaskini map:

iex> steve = %Example.User{name: "Steve"}
%Example.User<name: "Steve", roles: [...], ...>
iex> sean = %{steve | name: "Sean"}
%Example.User<name: "Sean", roles: [...], ...>

Paling penting, anda boleh membuat padanan antara struct dan map:

iex> %{name: "Sean"} = sean
%Example.User<name: "Sean", roles: [...], ...>

Komposisi

Setelah kita mengetahui cara untuk membina modul dan struct, kini masa untuk mempelajari cara untuk memasukkan kefungsian sedia ada ke dalam modul tersebut melalui komposisi. Elixir membekalkan kita beberapa cara berbeza untuk berinteraksi dengan modul-modul lain, mari lihat apa yang telah disediakan untuk kita.

alias

Membenarkan kita membuat alias nama modul, paling kerap digunakan dalam kod Elixir:

defmodule Sayings.Greetings do
  def basic(name), do: "Hi, #{name}"
end

defmodule Example do
  alias Sayings.Greetings

  def greeting(name), do: Greetings.basic(name)
end

# tanpa alias

defmodule Example do
  def greeting(name), do: Sayings.Greetings.basic(name)
end

Jika terdapat konflik antara dua alias, atau anda cuma mahu membuat alias kepada nama lain, kita boleh gunakan option :as:

defmodule Example do
  alias Sayings.Greetings, as: Hi

  def print_message(name), do: Hi.basic(name)
end

Ianya dibolehkan untuk alias pelbagai modul pada masa yang sama:

defmodule Example do
  alias Sayings.{Greetings, Farewells}
end

import

Jika kita tidak mahu menggunakan alias, kita boleh mengimport fungsi-fungsi dan makro-makro dengan menggunakan import/:

iex> last([1, 2, 3])
** (CompileError) iex:9: undefined function last/1
iex> import List
nil
iex> last([1, 2, 3])
3

Filtering

Apabila kita mengimport sesatu modul, semua fungsi dan makro di dalam modul tersebut akan diimport secara lalai tetapi kita boleh filter mereka dengan menggunakan option-option :only dan :except.

Apabila mengimport fungsi-fungsi dan makro-makro khusus kita mesti menyediakan pasangan ‘name/arity’ kepada :only dan :except. Kita akan mula mengimport hanya fungsi last/1:

iex> import List, only: [last: 1]
iex> first([1, 2, 3])
** (CompileError) iex:13: undefined function first/1
iex> last([1, 2, 3])
3

Jika kita mengimport kesemua fungsi-fungsi kecuali fungsi last/1:

iex> import List, except: [last: 1]
nil
iex> first([1, 2, 3])
1
iex> last([1, 2, 3])
** (CompileError) iex:3: undefined function last/1

Sebagai tambahan kepada pasangan ‘name/arity’ terdapat dua jenis atom istimewa, :functions dan :macros, yang masing-masing hanya mengimport fungsi dan makro.

import List, only: :functions
import List, only: :macros

require

Walaupun tidak kerap digunakan require/2 tidak kurang pentingnya. Kepemerluan satu modul memastikan ianya dikompilkan dan dipasang. Ianya amat berguna apabila kita perlu mencapai makro sesatu modul:

defmodule Example do
  require SuperMacros

  SuperMacros.do_stuff
end

Jika kita cuba memanggil satu makro yang masih belum dipasang Elixir akan menimbulkan ralat.

use

Menggunakan modul dalam konteks semasa. Ianya amat berguna apabila satu modul perlu melakukan beberapa tetapan. Dengan memanggil use, kita juga secara spontan memanggil ‘hook’__using__ di dalam modul tersebut, memberikan peluang kepada modul tersebut untuk membuat perubahan kepada konteks semasa:

defmodule MyModule do
  defmacro __using__(opts) do
    quote do
      import MyModule.Foo
      import MyModule.Bar
      import MyModule.Baz

      alias MyModule.Repo
    end
  end
end
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!