Typespec

Anotasi -spec dan -type untuk mendokumentasikan tipe dan mendukung dialyzer.

Daftar isi

Topik ini memperkenalkan typespec, bahasa anotasi tipe pada Erlang: atribut -spec yang menyatakan kontrak fungsi, atribut -type yang memberi nama pada bentuk data, dan tipe-tipe bawaan yang menjadi kosakatanya. Typespec tidak mengubah runtime sama sekali, melainkan dokumentasi yang dapat diperiksa mesin, dan menjadi masukan utama dialyzer pada topik dialyzer.

Kontrak dalam atribut#

Fungsi Erlang tidak menyatakan tipe apa pun, dan pilihan itu disengaja. Kebutuhan tetap ada: pemanggil perlu tahu argumen apa yang diterima dan apa yang kembali. Typespec menjawabnya dengan atribut yang ditulis tepat di atas fungsi dan hidup di dalam modul, sehingga kontrak berjalan bersama kodenya:

erlang statistik_tipe.erl
-module(statistik_tipe).
-export([rerata/1]).

-type nilai() :: number().
-type daftar_nilai() :: [nilai()].

-spec rerata(daftar_nilai()) -> float().
rerata([]) -> 0.0;
rerata(Nilai) -> lists:sum(Nilai) / length(Nilai).

Atribut -spec dibaca sebagai: fungsi rerata/1 menerima daftar nilai dan mengembalikan float. Atribut -type memberi nama pada bentuk data, dan nama itu boleh dipakai modul lain lewat kualifikasi statistik_tipe:daftar_nilai(). Kompilasi modul ini bersih karena typespec memang tidak dieksekusi. Kekuatannya baru terasa saat diperiksa oleh dialyzer atau dibaca oleh editor dan dokumentasi otomatis.

Kosakata tipe#

Typespec menyediakan tipe bawaan yang selaras dengan data Erlang. Yang paling sering dipakai adalah integer(), float(), number(), atom(), binary(), list(), map(), dan term() sebagai penampung apa pun. Bentuk turunannya sering lebih berguna daripada tipe dasar: daftar bertipe ditulis [nilai()], tuple ditulis {A, B}, jangkauan ditulis 1..10, subtipe bawaan ditulis pos_integer(), dan gabungan ditulis dengan tanda |. Tipe map berkunci tetap ditulis #{nama := binary(), usia := pos_integer()}, dan bentuk inilah yang lazim untuk data terstruktur modern. Anotasi pada native record EEP-79 yang dilihat pada modul Record dan string memakai kosakata yang sama.

Spec pada hasil bertanda dan tipe opaque#

Spec paling berguna biasanya ada pada bentuk hasil. Modul berikut menamai pola hasil bertanda sebagai tipe dan menjaminnya dengan spec:

erlang pesan_tipe.erl
-module(pesan_tipe).
-export([validasi/1]).

-type hasil() :: {ok, pos_integer()} | {error, atom()}.

-spec validasi(term()) -> hasil().
validasi(N) when is_integer(N), N > 0 -> {ok, N};
validasi(_) -> {error, bukan_positif}.

Tipe hasil() mendokumentasikan dua wajah kembalian sekaligus, dan term() pada argumen menyatakan bahwa apa pun boleh diberikan. Hasil eksekusinya mengikuti spec persis:

erlang Mencoba fungsi hasil bertanda
1> c(pesan_tipe).
{ok,pesan_tipe}
2> pesan_tipe:validasi(7).
{ok,7}
3> pesan_tipe:validasi(x).
{error,bukan_positif}

Modul ini juga lolos dialyzer tanpa temuan, yang menegaskan spec-nya jujur. Satu alat tambahan terakhir adalah -opaque yang menyembunyikan bentuk tipe dari luar modul: pembaca modul tahu nama tipe itu tetapi tidak boleh menguraikan isinya, sehingga perubahan tata letak internal tidak merusak pemanggil. Opaque menuntut fungsi akses resmi untuk setiap kebutuhan, dan cocok untuk tipe yang bentuk internalnya merupakan rahasia desain.

Nilai sebuah spec ditentukan kejujurannya. Spec yang terlalu longgar seperti term() pada semua tempat tidak memberi informasi apa pun, sedangkan yang terlalu sempit berbohong dan menggiring dialyzer ke konflik palsu. Tiga kebiasaan menjaga keseimbangan: sebut tipe paling sempit yang benar-benar diterima fungsi, gunakan -type untuk memberi nama pada bentuk yang berulang sehingga artinya terdokumentasi sekali, dan perbarui spec bersamaan dengan perubahan fungsinya. Spec juga menjadi tempat pola nilai kembalian yang terdokumentasi, misalnya {ok, Data} | {error, Alasan} untuk fungsi yang melaporkan kegagalan sebagai nilai.

Perlu diingat bahwa compiler memeriksa sintaks spec tetapi tidak memeriksa kesesuaiannya dengan kode. Fungsi yang kembaliannya bertentangan dengan spec-nya tetap dikompilasi bersih, dan penangkapnya adalah pekerjaan dialyzer. Pembagian tugas ini menjelaskan mengapa kedua alat itu selalu disebut berpasangan pada kode produksi Erlang.

Spec pada callback dan dokumentasi modul#

Spec tidak hanya untuk fungsi biasa. Behaviour OTP menyatakan kontrak callbacknya dengan atribut -callback, yang bentuknya sama dengan -spec tetapi berlaku untuk setiap modul yang memakai behaviour tersebut, sehingga callback yang hilang atau bertipe keliru terlihat sebagai peringatan compiler. Untuk dokumentasi, OTP 29 memperkenalkan atribut -doc yang menyimpan teks dokumentasi pada modul dan fungsi, dan contoh shell di dalamnya dapat dieksekusi nyata oleh ct_doctest sebagaimana diperlihatkan pada topik doctest di modul ini. Kombinasi keduanya menutup siklus yang sama: kontrak tipe dijaga dialyzer, kontrak dokumentasi dijaga doctest, dan keduanya hidup dalam berkas yang sama dengan kodenya.

Ringkasan#

  • -spec menyatakan kontrak tipe fungsi dan -type memberi nama pada bentuk data yang berulang.
  • Typespec tidak memengaruhi runtime. Nilainya ada pada dokumentasi mesin yang diperiksa dialyzer dan alat lain.
  • Kosakatanya selaras dengan data Erlang, termasuk daftar bertipe, tuple, jangkauan, gabungan, dan map bertipe.
  • Spec yang jujur memilih tipe paling sempit yang benar, dan compiler tidak memeriksa kesesuaiannya dengan kode.

Referensi#