Apakah ide yang buruk untuk mengembalikan tipe data yang berbeda dari satu fungsi dalam bahasa yang diketik secara dinamis?


65

Bahasa utama saya diketik secara statis (Jawa). Di Jawa, Anda harus mengembalikan satu jenis dari setiap metode. Misalnya, Anda tidak dapat memiliki metode yang mengembalikan secara Stringkondisional atau mengembalikan secara kondisional Integer. Tetapi dalam JavaScript, misalnya, ini sangat mungkin.

Dalam bahasa yang diketik secara statis saya mengerti mengapa ini adalah ide yang buruk. Jika setiap metode dikembalikan Object(orang tua umum semua kelas mewarisi dari) maka Anda dan kompiler tidak tahu apa yang Anda hadapi. Anda harus menemukan semua kesalahan saat dijalankan.

Tetapi dalam bahasa yang diketik secara dinamis, bahkan mungkin tidak ada kompiler. Dalam bahasa yang diketik secara dinamis, tidak jelas bagi saya mengapa fungsi yang mengembalikan beberapa jenis adalah ide yang buruk. Latar belakang saya dalam bahasa statis membuat saya menghindari penulisan fungsi-fungsi seperti itu, tetapi saya khawatir saya terlalu dekat dengan fitur yang bisa membuat kode saya lebih bersih dengan cara yang tidak bisa saya lihat.


Sunting : Saya akan menghapus contoh saya (sampai saya bisa memikirkan yang lebih baik). Saya pikir itu mengarahkan orang untuk menjawab suatu hal yang tidak saya coba sampaikan.


Mengapa tidak melempar pengecualian dalam kasus kesalahan?
TrueWill

1
@ Benar, saya akan mengatasinya dalam kalimat berikutnya.
Daniel Kaplan

Pernahkah Anda memperhatikan bahwa ini adalah praktik umum dalam bahasa yang lebih dinamis? Ini umum dalam bahasa fungsional , tetapi di sana aturan dibuat sangat eksplisit. Dan saya tahu bahwa itu umum, khususnya dalam JavaScript, untuk memungkinkan argumen menjadi tipe yang berbeda (karena pada dasarnya itulah satu-satunya cara untuk melakukan fungsi yang berlebihan), tetapi saya jarang melihat ini diterapkan untuk mengembalikan nilai. Satu-satunya contoh yang dapat saya pikirkan adalah PowerShell dengan sebagian besar array otomatisnya membungkus / membuka di mana-mana, dan bahasa scripting adalah sedikit kasus luar biasa.
Aaronaught

3
Tidak disebutkan, tetapi ada banyak contoh (bahkan dalam Java Generics) fungsi yang mengambil tipe-kembali sebagai parameter; misalnya dalam Common Lisp kita memiliki (coerce var 'string)hasil stringatau (concatenate 'string this that the-other-thing)juga. Saya sudah menulis hal-hal seperti ThingLoader.getThingById (Class<extends FindableThing> klass, long id)juga. Dan, di sana, saya mungkin hanya mengembalikan sesuatu yang mensubclasskan apa yang Anda minta: loader.getThingById (SubclassA.class, 14)mungkin mengembalikan SubclassByang meluas SubclassA...
BRPocock

1
Bahasa yang diketik secara dinamis seperti Sendok dalam film The Matrix . Jangan mencoba mendefinisikan Sendok sebagai string atau angka . Itu tidak mungkin. Alih-alih ... hanya mencoba memahami kebenaran. Bahwa tidak ada sendok .
Reactgular

Jawaban:


42

Berbeda dengan jawaban lain, ada kasus di mana mengembalikan berbagai jenis dapat diterima.

Contoh 1

sum(2, 3)  int
sum(2.1, 3.7)  float

Dalam beberapa bahasa yang diketik secara statis, ini melibatkan kelebihan beban, jadi kami dapat mempertimbangkan bahwa ada beberapa metode, masing-masing mengembalikan jenis yang telah ditentukan sebelumnya, jenis tetap. Dalam bahasa dinamis, ini mungkin fungsi yang sama, diimplementasikan sebagai:

var sum = function (a, b) {
    return a + b;
};

Fungsi yang sama, berbagai jenis nilai pengembalian.

Contoh 2

Bayangkan Anda mendapat respons dari komponen OpenID / OAuth. Beberapa penyedia OpenID / OAuth mungkin berisi lebih banyak informasi, seperti usia orang tersebut.

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Facebook',
//     name: {
//         firstName: 'Hello',
//         secondName: 'World',
//     },
//     email: 'hello.world@example.com',
//     age: 27
// }

Orang lain akan memiliki minimum, apakah itu alamat email atau nama samaran.

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Google',
//     email: 'hello.world@example.com'
// }

Sekali lagi, fungsi yang sama, hasil yang berbeda.

Di sini, manfaat mengembalikan jenis yang berbeda sangat penting dalam konteks di mana Anda tidak peduli dengan jenis dan antarmuka, tetapi benda apa yang sebenarnya berisi. Misalnya, mari kita bayangkan sebuah situs web berisi bahasa dewasa. Maka findCurrent()dapat digunakan seperti ini:

var user = authProvider.findCurrent();
if (user.age || 0 >= 16) {
    // The person can stand mature language.
    allowShowingContent();
} else if (user.age) {
    // OpenID/OAuth gave the age, but the person appears too young to see the content.
    showParentalAdvisoryRequestedMessage();
} else {
    // OpenID/OAuth won't tell the age of the person. Ask the user himself.
    askForAge();
}

Refactoring ini ke dalam kode di mana setiap penyedia akan memiliki fungsinya sendiri yang akan mengembalikan tipe yang terdefinisi dengan baik dan tidak hanya akan menurunkan basis kode dan menyebabkan duplikasi kode, tetapi juga tidak akan membawa manfaat apa pun. Seseorang mungkin akhirnya melakukan kengerian seperti:

var age;
if (['Facebook', 'Yahoo', 'Blogger', 'LiveJournal'].contains(user.provider)) {
    age = user.age;
}

5
"Dalam bahasa yang diketik secara statis, ini melibatkan kelebihan" Saya pikir maksud Anda "dalam beberapa bahasa yang diketik secara statis" :) Bahasa yang diketik secara statis baik tidak memerlukan kelebihan beban untuk sesuatu seperti sumcontoh Anda .
Andres F.

17
Sebagai contoh spesifik Anda, pertimbangkan Haskell: sum :: Num a => [a] -> a. Anda dapat menjumlahkan daftar apa pun yang merupakan angka. Tidak seperti javascript, jika Anda mencoba menjumlahkan sesuatu yang bukan angka, kesalahan akan ditangkap pada waktu kompilasi.
Andres F.

3
@MainMa Dalam Scala, Iterator[A]memiliki metode def sum[B >: A](implicit num: Numeric[B]): B, yang sekali lagi memungkinkan untuk menjumlahkan semua jenis angka dan diperiksa pada waktu kompilasi.
Petr Pudlák

3
@ Bakuriu Tentu saja Anda dapat menulis fungsi seperti itu, meskipun Anda pertama-tama harus kelebihan beban +untuk string (dengan mengimplementasikan Num, yang merupakan ide yang buruk, desain bijaksana tetapi legal) atau menciptakan operator / fungsi yang berbeda yang kelebihan beban oleh bilangan bulat dan string masing-masing. Haskell memiliki polimorfisme ad-hoc melalui kelas tipe. Daftar yang berisi bilangan bulat dan string jauh lebih sulit (mungkin tidak mungkin tanpa ekstensi bahasa) tetapi masalah yang agak berbeda.

2
Saya tidak terlalu terkesan dengan contoh kedua Anda. Kedua objek itu adalah "tipe" konseptual yang sama, hanya saja dalam beberapa kasus bidang tertentu tidak didefinisikan atau diisi. Anda dapat mewakili itu dengan baik bahkan dalam bahasa yang diketik secara statis dengan nullnilai.
Aaronaught

31

Secara umum, ini adalah ide yang buruk karena alasan yang sama dengan persamaan moral dalam bahasa yang diketik secara statis adalah ide yang buruk: Anda tidak tahu tipe konkret dikembalikan, jadi Anda tidak tahu apa yang dapat Anda lakukan dengan hasilnya (selain dari beberapa hal yang dapat dilakukan dengan nilai apa pun). Dalam sistem tipe statis, Anda memiliki anotasi yang diperiksa kompiler untuk jenis kembali dan semacamnya, tetapi dalam bahasa dinamis pengetahuan yang sama masih ada - itu hanya informal dan disimpan dalam otak dan dokumentasi daripada dalam kode sumber.

Dalam banyak kasus, bagaimanapun, ada sajak dan alasan yang jenis dikembalikan dan efeknya mirip dengan kelebihan beban atau polimorfisme parametrik dalam sistem tipe statis. Dengan kata lain, jenis hasil yang diprediksi, itu hanya tidak cukup sederhana untuk mengekspresikan.

Tetapi perhatikan bahwa mungkin ada alasan lain mengapa fungsi tertentu dirancang dengan buruk: Misalnya, sumfungsi mengembalikan false pada input yang tidak valid adalah ide yang buruk terutama karena nilai pengembalian tidak berguna dan rentan kesalahan (0 <-> kebingungan salah).


40
Dua sen saya: Saya benci kalau itu terjadi. Saya telah melihat perpustakaan JS yang mengembalikan nol ketika tidak ada hasil, Obyek pada satu hasil, dan Array pada dua hasil atau lebih. Jadi, alih-alih hanya mengulang-ulang array, Anda dipaksa untuk menentukan apakah itu null, dan jika bukan, jika array, dan jika ya, lakukan satu hal, jika tidak lakukan sesuatu yang lain. Terutama karena, dalam pengertian normal, setiap pengembang waras hanya akan menambahkan Objek ke Array dan mendaur ulang logika program dari penanganan array.
phyrfox

10
@Izkata: Saya tidak berpikir NaN"tandingan" sama sekali. NaN sebenarnya adalah input yang valid untuk setiap perhitungan floating-point. Anda dapat melakukan apa saja dengan NaNyang dapat Anda lakukan dengan angka aktual. Benar, hasil akhir dari perhitungan tersebut mungkin tidak terlalu berguna bagi Anda, tetapi itu tidak akan menyebabkan kesalahan runtime yang aneh dan Anda tidak perlu memeriksanya setiap saat - Anda hanya dapat memeriksa sekali di akhir serangkaian perhitungan. NaN bukan tipe yang berbeda , hanya nilai khusus , semacam Null Object .
Aaronaught

5
@Aonaonaught Di sisi lain NaN,, seperti objek nol, cenderung bersembunyi ketika kesalahan terjadi, hanya untuk mereka muncul nanti. Misalnya, program Anda kemungkinan besar akan meledak jika NaNmasuk ke loop kondisional.
Izkata

2
@Izkata: NaN dan null memiliki perilaku yang sama sekali berbeda. Referensi nol akan meledak segera setelah akses, sebuah NaN merambat. Mengapa yang terakhir terjadi sangat jelas, ini memungkinkan Anda untuk menulis ekspresi matematika tanpa harus memeriksa hasil setiap sub-ekspresi. Secara pribadi saya melihat null jauh lebih merusak, karena NaN mewakili konsep numerik yang tepat yang tidak dapat Anda hilangkan tanpa dan, secara matematis, menyebarkan adalah perilaku yang benar.
Phoshi

4
Saya tidak tahu mengapa ini berubah menjadi argumen "nol vs. yang lain". Saya hanya menunjukkan bahwa NaNnilainya adalah (a) sebenarnya bukan tipe pengembalian yang berbeda dan (b) memiliki semantik floating-point yang terdefinisi dengan baik, dan karena itu benar-benar tidak memenuhi syarat sebagai analog dengan nilai pengembalian yang diketik secara bervariasi. Tidak semua yang berbeda dari nol dalam aritmatika integer, sungguh; jika nol "tanpa sengaja" masuk ke dalam perhitungan Anda, maka Anda sering berakhir dengan nol sebagai akibat atau kesalahan divide-by-zero. Apakah nilai "infinity" yang didefinisikan oleh IEEE floating point juga jahat?
Aaronaught

26

Dalam bahasa dinamis, Anda tidak boleh bertanya apakah mengembalikan tipe berbeda tetapi objek dengan API berbeda . Karena sebagian besar bahasa dinamis tidak terlalu peduli tentang jenis, tetapi gunakan berbagai versi pengetikan bebek .

Ketika kembali berbagai jenis masuk akal

Misalnya metode ini masuk akal:

def load_file(file): 
    if something: 
       return ['a ', 'list', 'of', 'strings'] 
    return open(file, 'r')

Karena kedua file, dan daftar string adalah (dengan Python) iterables yang mengembalikan string. Tipe yang sangat berbeda, API yang sama (kecuali seseorang mencoba memanggil metode file pada daftar, tetapi ini adalah cerita yang berbeda).

Anda dapat kembali secara kondisional listatau tuple( tupleadalah daftar yang tidak dapat diubah dengan python).

Bahkan secara formal melakukan:

def do_something():
    if ...: 
        return None
    return something_else

atau:

function do_something(){
   if (...) return null; 
   return sth;
}

mengembalikan tipe yang berbeda, karena kedua python Nonedan Javascript nulladalah tipe di kanan mereka sendiri.

Semua kasus penggunaan ini akan memiliki rekan mereka dalam bahasa statis, fungsinya hanya akan mengembalikan antarmuka yang tepat.

Ketika mengembalikan objek dengan API yang berbeda secara kondisional adalah ide yang bagus

Mengenai apakah mengembalikan API yang berbeda adalah ide yang baik, IMO dalam banyak kasus itu tidak masuk akal. Hanya contoh masuk akal yang muncul di pikiran adalah sesuatu yang dekat dengan apa yang dikatakan @MainMa : ketika API Anda dapat memberikan jumlah detail yang bervariasi, mungkin masuk akal untuk mengembalikan lebih detail ketika tersedia.


4
Jawaban yang bagus. Perlu dicatat bahwa Java / diketik setara secara setara adalah memiliki fungsi mengembalikan antarmuka daripada jenis beton tertentu, dengan antarmuka yang mewakili API / abstraksi.
mikera

1
Saya pikir Anda nitpicking, dalam Python daftar dan tuple mungkin dari "jenis" yang berbeda, tetapi mereka dari "jenis bebek" yang sama, yaitu mereka mendukung serangkaian operasi yang sama. Dan kasus-kasus seperti inilah yang menjadi alasan mengapa mengetik bebek telah diperkenalkan. Anda dapat melakukan lama x = getInt () di Jawa juga.
Kaerber

"Mengembalikan berbagai jenis" berarti "mengembalikan objek dengan API yang berbeda". (Beberapa bahasa membuat cara objek dibangun menjadi bagian mendasar dari API, beberapa tidak; itu masalah seberapa ekspresifnya sistem tipe tersebut.) Dalam daftar / file contoh Anda, do_filemengembalikan sebuah string yang dapat diubah dengan cara apa pun.
Gilles 'SANGAT berhenti menjadi jahat'

1
Dalam contoh Python, Anda juga bisa memanggil iter()daftar dan file untuk memastikan hasilnya hanya dapat digunakan sebagai iterator dalam kedua kasus.
RemcoGerlich

1
kembali None or somethingadalah pembunuh untuk optimasi kinerja yang dilakukan oleh alat-alat seperti PyPy, Numba, Pyston dan lainnya. Ini adalah salah satu dari Python-isme yang membuat python tidak terlalu cepat.
Matt

9

Pertanyaan Anda membuat saya ingin sedikit menangis. Bukan untuk penggunaan contoh yang Anda berikan, tetapi karena seseorang tanpa disadari akan mengambil pendekatan ini terlalu jauh. Ini adalah langkah singkat dari kode yang sangat sulit dipelihara.

Kondisi kesalahan menggunakan jenis kasus masuk akal, dan pola nol (semuanya harus menjadi pola) dalam bahasa yang diketik secara statis melakukan hal yang sama. Panggilan fungsi Anda mengembalikan objectatau mengembalikannya null.

Tapi itu langkah singkat untuk mengatakan "Aku akan menggunakan ini untuk membuat pola pabrik " dan kembali baik fooatau baratau baztergantung suasana hati fungsi ini. Debugging ini akan menjadi mimpi buruk ketika penelepon mengharapkan footetapi diberikan bar.

Jadi saya tidak berpikir Anda sedang berpikiran tertutup. Anda berhati-hati dalam menggunakan fitur bahasa.

Pengungkapan: latar belakang saya dalam bahasa yang diketik secara statis, dan saya umumnya bekerja pada tim yang lebih besar dan beragam di mana kebutuhan akan kode yang dapat dipelihara cukup tinggi. Jadi perspektif saya mungkin miring juga.


6

Menggunakan Generics in Java memungkinkan Anda mengembalikan tipe yang berbeda, namun tetap mempertahankan keamanan tipe statis. Anda cukup menentukan tipe yang ingin Anda kembalikan dalam parameter tipe generik dari panggilan fungsi.

Apakah Anda bisa menggunakan pendekatan serupa di Javascript, tentu saja, merupakan pertanyaan terbuka. Karena Javascript adalah bahasa yang diketik secara dinamis, mengembalikan sebuah objectsepertinya pilihan yang jelas.

Jika Anda ingin tahu di mana skenario pengembalian dinamis dapat berfungsi saat Anda terbiasa bekerja dalam bahasa yang diketik secara statis, pertimbangkan untuk melihat dynamickata kunci dalam C #. Rob Conery berhasil menulis Object-Relational Mapper dalam 400 baris kode menggunakan dynamickata kunci.

Tentu saja, semua dynamicsebenarnya adalah membungkus objectvariabel dengan beberapa jenis keamanan runtime.


4

Itu ide yang buruk, saya pikir, untuk mengembalikan tipe yang berbeda secara kondisional. Salah satu cara ini sering muncul bagi saya adalah jika fungsinya dapat mengembalikan satu atau lebih nilai. Jika hanya satu nilai yang perlu dikembalikan, mungkin masuk akal untuk mengembalikan nilai, daripada mengemasnya dalam Array, untuk menghindari keharusan membongkar dalam fungsi panggilan. Namun, ini (dan sebagian besar contoh lain dari ini) menempatkan kewajiban pada penelepon untuk membedakan antara dan menangani kedua jenis. Fungsi akan lebih mudah untuk dipikirkan jika selalu mengembalikan jenis yang sama.


4

"Praktek buruk" ada terlepas dari apakah bahasa Anda diketik secara statis. Bahasa statis lebih banyak menjauhkan Anda dari praktik-praktik ini, dan Anda mungkin menemukan lebih banyak pengguna yang mengeluh tentang "praktik buruk" dalam bahasa statis karena itu adalah bahasa yang lebih formal. Namun, masalah yang mendasarinya ada dalam bahasa yang dinamis, dan Anda dapat menentukan apakah mereka dibenarkan.

Inilah bagian yang tidak menyenangkan dari apa yang Anda usulkan. Jika saya tidak tahu jenis apa yang dikembalikan, maka saya tidak dapat langsung menggunakan nilai kembali. Saya harus "menemukan" sesuatu tentang itu.

total = sum_of_array([20, 30, 'q', 50])
if (type_of(total) == Boolean) {
  display_error(...)
} else {
  record_number(total)
}

Seringkali jenis pengalihan kode ini merupakan praktik yang buruk. Itu membuat kode lebih sulit dibaca. Dalam contoh ini, Anda melihat mengapa melempar dan menangkap kasus pengecualian sangat populer. Dengan kata lain: jika fungsi Anda tidak dapat melakukan apa yang dikatakannya, itu tidak seharusnya kembali dengan sukses . Jika saya memanggil fungsi Anda, saya ingin melakukan ini:

total = sum_of_array([20, 30, 'q', 50])
display_number(total)

Karena baris pertama kembali dengan sukses, saya berasumsi bahwa totalsebenarnya berisi jumlah array. Jika tidak berhasil kembali, kami beralih ke halaman lain dari program saya.

Mari kita gunakan contoh lain yang bukan hanya tentang menyebarkan kesalahan. Mungkin sum_of_array mencoba menjadi pintar dan mengembalikan string yang dapat dibaca manusia dalam beberapa kasus, seperti "Itu kombinasi loker saya!" jika dan hanya jika arraynya adalah [11,7,19]. Saya mengalami kesulitan memikirkan contoh yang baik. Bagaimanapun, masalah yang sama berlaku. Anda harus memeriksa nilai pengembalian sebelum Anda dapat melakukan apa pun dengan itu:

total = sum_of_array([20, 30, 40, 50])
if (type_of(total) == String) {
  write_message(total)
} else {
  record_number(total)
}

Anda mungkin berpendapat bahwa akan berguna untuk fungsi mengembalikan integer atau float, misalnya:

sum_of_array(20, 30, 40) -> int
sum_of_array(23.45, 45.67, 67.789044) -> float

Tetapi hasil itu bukan tipe yang berbeda, sejauh yang Anda ketahui. Anda akan memperlakukan mereka berdua sebagai angka, dan hanya itu yang Anda pedulikan. Jadi sum_of_array mengembalikan tipe angka. Inilah yang dimaksud dengan polimorfisme.

Jadi ada beberapa praktik yang mungkin Anda langgar jika fungsi Anda dapat mengembalikan beberapa tipe. Mengetahui mereka akan membantu Anda menentukan apakah fungsi khusus Anda akan mengembalikan beberapa jenis.


Maaf, saya menyesatkan Anda dengan teladan saya yang buruk. Lupakan saya sebutkan di tempat pertama. Paragraf pertama Anda tepat sasaran. Saya juga suka contoh kedua Anda.
Daniel Kaplan

4

Sebenarnya, tidak jarang sama sekali untuk mengembalikan tipe yang berbeda bahkan dalam bahasa yang diketik secara statis. Itu sebabnya kami memiliki jenis serikat pekerja, misalnya.

Bahkan, metode di Jawa hampir selalu mengembalikan satu dari empat jenis: beberapa jenis objek atau nullpengecualian atau mereka tidak pernah kembali sama sekali.

Dalam banyak bahasa, kondisi kesalahan dimodelkan sebagai subrutin yang mengembalikan tipe hasil atau tipe kesalahan. Misalnya, dalam Scala:

def transferMoney(amount: Decimal): Either[String, Decimal]

Ini adalah contoh yang bodoh, tentu saja. Jenis kembali berarti "mengembalikan string atau desimal". Secara konvensi, tipe kiri adalah tipe kesalahan (dalam hal ini, string dengan pesan kesalahan) dan jenis yang tepat adalah jenis hasil.

Ini mirip dengan pengecualian, kecuali kenyataan bahwa pengecualian juga merupakan konstruk aliran kontrol. Mereka, pada kenyataannya, setara dalam kekuatan ekspresif GOTO.


Metode di Jawa mengembalikan pengecualian terdengar aneh. " Kembali sebuah pengecualian " ... hmm
nyamuk

3
Pengecualian adalah salah satu hasil yang mungkin dari suatu metode di Jawa. Sebenarnya, saya lupa tipe keempat: Unit(metode tidak diperlukan untuk kembali sama sekali). Benar, Anda tidak benar-benar menggunakan returnkata kunci, tetapi hasilnya adalah kembali dari metode. Dengan pengecualian yang dicentang, itu bahkan secara eksplisit disebutkan dalam tipe tanda tangan.
Jörg W Mittag

Saya melihat. Maksud Anda tampaknya memiliki beberapa kelebihan, tetapi cara itu disajikan tidak membuatnya terlihat menarik ... agak berlawanan
nyamuk

4
Dalam banyak bahasa, kondisi kesalahan dimodelkan sebagai subrutin yang mengembalikan tipe hasil atau tipe kesalahan. Pengecualian adalah ide yang sama, kecuali mereka juga mengontrol konstruksi aliran (sama dalam kekuatan ekspresif dengan GOTO, sebenarnya).
Jörg W Mittag

4

Belum ada jawaban yang menyebutkan prinsip SOLID. Secara khusus, Anda harus mengikuti prinsip substitusi Liskov, bahwa setiap kelas yang menerima tipe selain tipe yang diharapkan masih dapat bekerja dengan apa pun yang mereka dapatkan, tanpa melakukan apa pun untuk menguji jenis apa yang dikembalikan.

Jadi, jika Anda melempar beberapa properti tambahan ke objek, atau membungkus fungsi yang dikembalikan dengan semacam dekorator yang masih memenuhi apa yang ingin dicapai fungsi asli, Anda baik, selama tidak ada kode yang memanggil fungsi Anda pernah bergantung pada ini perilaku, di jalur kode apa pun.

Alih-alih mengembalikan string atau integer, contoh yang lebih baik mungkin mengembalikan sistem sprinkler atau kucing. Ini bagus jika semua kode panggilan yang akan dilakukan adalah memanggil functionInQuestion.hiss (). Secara efektif Anda memiliki antarmuka tersirat yang diharapkan oleh kode panggilan, dan bahasa yang diketik secara dinamis tidak akan memaksa Anda untuk membuat antarmuka menjadi eksplisit.

Sedihnya, rekan kerja Anda mungkin akan melakukannya, jadi Anda mungkin harus melakukan pekerjaan yang sama dalam dokumentasi Anda, kecuali tidak ada cara yang dapat diterima secara universal, singkat, dan dapat dianalisis dengan mesin untuk melakukannya - seperti saat Anda mendefinisikan antarmuka dalam bahasa yang memilikinya.


3

Satu tempat di mana saya melihat diri saya mengirim berbagai jenis adalah untuk input yang tidak valid atau "pengecualian orang miskin" di mana kondisi "luar biasa" tidak terlalu luar biasa. Sebagai contoh, dari repositori fungsi-fungsi utilitas PHP contoh singkat ini:

function ensure_fields($consideration)
{
        $args = func_get_args();
        foreach ( $args as $a ) {
                if ( !is_string($a) ) {
                        return NULL;
                }
                if ( !isset($consideration[$a]) || $consideration[$a]=='' ) {
                        return FALSE;
                }
        }

        return TRUE;
}

Fungsi secara nominal mengembalikan BOOLEAN, namun mengembalikan NULL pada input yang tidak valid. Perhatikan bahwa sejak PHP 5.3, semua fungsi PHP internal juga berperilaku seperti ini . Selain itu, beberapa fungsi PHP internal mengembalikan FALSE atau INT pada input nominal, lihat:

strpos('Hello', 'e');  // Returns INT(1)
strpos('Hello', 'q');  // Returns BOOL(FALSE)

4
Dan inilah mengapa saya membenci PHP. Salah satu alasannya.
Aaronaught

1
A downvote karena seseorang tidak menyukai bahasa yang saya kembangkan secara profesional?!? Tunggu sampai mereka tahu bahwa saya Yahudi! :)
dotancohen

3
Jangan selalu berasumsi bahwa orang-orang yang berkomentar dan orang-orang yang downvote adalah orang yang sama.
Aaronaught

1
Saya lebih suka memiliki pengecualian daripada null, karena (a) gagal keras , jadi lebih mungkin diperbaiki pada fase pengembangan / pengujian, (b) mudah untuk ditangani, karena saya bisa menangkap semua pengecualian langka / tidak terduga satu kali untuk seluruh aplikasi saya (di front controller saya) dan hanya login (saya biasanya mengirim email ke tim dev), sehingga bisa diperbaiki nanti. Dan saya benar-benar benci bahwa pustaka PHP standar kebanyakan menggunakan pendekatan "kembali null" - itu benar-benar hanya membuat kode PHP lebih rentan kesalahan, kecuali Anda memeriksa semuanya dengan isset(), yang terlalu banyak beban.
scriptin

1
Juga, jika Anda kembali nullkarena kesalahan, tolong harap jelaskan dalam docblock (mis. @return boolean|null), Jadi jika saya menemukan kode Anda suatu hari nanti, saya tidak perlu memeriksa fungsi / metode body.
scriptin

3

Saya rasa ini bukan ide yang buruk! Berbeda dengan pendapat yang paling umum ini, dan seperti yang telah ditunjukkan oleh Robert Harvey, bahasa yang diketik secara statis seperti Java memperkenalkan Generics persis untuk situasi seperti yang Anda tanyakan. Sebenarnya Java mencoba untuk menjaga (sedapat mungkin) keamanan ketik pada waktu kompilasi, dan terkadang Generics menghindari duplikasi kode, mengapa? Karena Anda dapat menulis metode yang sama atau kelas yang sama yang menangani / mengembalikan tipe yang berbeda . Saya akan membuat contoh yang sangat singkat untuk menunjukkan ide ini:

Jawa 1.4

public static Boolean getBoolean(String property){
    return (Boolean) properties.getProperty(property);
}
public static Integer getInt(String property){
    return (Integer) properties.getProperty(property);
}

Java 1.5+

public static <T> getValue(String property, Class<T> clazz) throws WhateverCheckedException{
    return clazz.getConstructor(String.class).newInstance(properties.getProperty(property));
}
//the call will be
Boolean b = getValue("useProxy",Boolean.class);
Integer b = getValue("proxyPort",Integer.class);

Dalam bahasa yang diketik dinamis, karena Anda tidak memiliki ketelitian pada waktu kompilasi, Anda benar-benar bebas untuk menulis kode yang sama yang bekerja untuk banyak jenis. Karena bahkan dalam bahasa yang diketik secara statis, Generik diperkenalkan untuk menyelesaikan masalah ini, jelas merupakan petunjuk bahwa menulis fungsi yang mengembalikan berbagai jenis dalam bahasa dinamis bukanlah ide yang buruk.


Downvote lain tanpa penjelasan! Terima kasih komunitas SE!
thermz

2
Miliki simpati +1. Anda harus mencoba membuka jawaban Anda dengan jawaban yang lebih jelas dan langsung, dan jangan mengomentari jawaban yang berbeda. (Saya akan memberi Anda +1 lagi, karena saya setuju dengan Anda, tetapi saya hanya punya satu untuk diberikan.)
DougM

3
Generik tidak ada dalam bahasa yang diketik secara dinamis (yang saya tahu). Tidak akan ada alasan bagi mereka untuk melakukannya. Kebanyakan obat generik adalah kasus khusus, di mana "wadah" lebih menarik daripada apa yang ada di dalamnya (itulah sebabnya beberapa bahasa, seperti Jawa, benar-benar menggunakan tipe erasure). Jenis generik sebenarnya adalah jenisnya sendiri. Metode generik , tanpa tipe generik yang sebenarnya, seperti dalam contoh Anda di sini, hampir selalu hanya berupa sintaksis gula untuk semantik assign-and-cast. Bukan contoh yang meyakinkan tentang pertanyaan yang sebenarnya ditanyakan.
Aaronaught

1
Saya mencoba menjadi sedikit lebih sintaksis: "Karena bahkan dalam bahasa yang diketik secara statis, Generik diperkenalkan untuk menyelesaikan masalah ini, itu jelas petunjuk bahwa menulis fungsi yang mengembalikan berbagai jenis dalam bahasa yang dinamis bukanlah ide yang buruk." Lebih baik sekarang? Periksa jawaban Robert Harvey atau komentar BRPocock dan Anda akan menyadari bahwa contoh Generik terkait dengan pertanyaan ini
thermz

1
Jangan merasa buruk, @thermz. Downvotes sering mengatakan lebih banyak tentang pembaca daripada penulis.
Karl Bielefeldt

2

Mengembangkan perangkat lunak pada dasarnya adalah seni dan keterampilan mengelola kompleksitas. Anda mencoba mempersempit sistem pada titik yang Anda mampu, dan membatasi opsi di titik lain. Antarmuka fungsi adalah kontrak yang membantu mengelola kompleksitas kode dengan membatasi pengetahuan yang diperlukan untuk bekerja dengan bagian kode apa pun. Dengan mengembalikan jenis yang berbeda, Anda secara signifikan memperluas antarmuka fungsi dengan menambahkan semua antarmuka dari jenis yang berbeda yang Anda kembalikan DAN menambahkan aturan yang tidak jelas tentang antarmuka yang dikembalikan.


1

Perl banyak menggunakan ini karena apa fungsi tidak tergantung pada konteksnya . Misalnya, suatu fungsi dapat mengembalikan array jika digunakan dalam konteks daftar, atau panjang array jika digunakan di tempat di mana nilai skalar diharapkan. Dari tutorial itu adalah hit pertama untuk "konteks perl" , jika Anda melakukannya:

my @now = localtime();

Kemudian @now adalah variabel array (itulah arti dari @), dan akan berisi array seperti (40, 51, 20, 9, 0, 109, 5, 8, 0).

Jika sebaliknya, Anda memanggil fungsi dengan cara di mana hasilnya harus berupa skalar, dengan ($ variabel skalar):

my $now = localtime();

maka ia melakukan sesuatu yang sama sekali berbeda: $ sekarang akan menjadi sesuatu seperti "Jumat 9 Januari 20:51:40 2009".

Contoh lain yang dapat saya pikirkan adalah dalam mengimplementasikan REST API, di mana format apa yang dikembalikan tergantung pada apa yang diinginkan klien. Misalnya, HTML, atau JSON, atau XML. Meskipun secara teknis itu semua aliran byte, idenya serupa.


Anda mungkin ingin menyebutkan cara spesifik dalam melakukan ini dalam perl - wantarray ... dan mungkin tautan ke beberapa diskusi jika itu baik atau buruk - Penggunaan wantarray Dianggap Berbahaya di PerlMonks

Secara kebetulan, saya berurusan dengan ini dengan Java Rest API saya sedang membangun. Saya memungkinkan satu sumber daya untuk mengembalikan XML atau JSON. Jenis penyebut umum terendah dalam kasus itu adalah a String. Sekarang orang-orang meminta dokumentasi tentang tipe pengembalian. Alat java dapat menghasilkan secara otomatis jika saya mengembalikan kelas, tetapi karena saya memilih Stringsaya tidak dapat menghasilkan dokumentasi secara otomatis. Di belakang / moral cerita, saya berharap saya mengembalikan satu jenis per metode.
Daniel Kaplan

Secara ketat, ini sama dengan bentuk kelebihan beban. Dengan kata lain, ada beberapa fungsi gabungan, dan tergantung pada rincian panggilan, satu atau versi lain dipilih.
Ian

1

Di tanah yang dinamis itu semua tentang mengetik bebek. Hal yang paling bertanggung jawab untuk diekspos / menghadap publik adalah untuk membungkus tipe yang berpotensi berbeda dalam pembungkus yang memberi mereka antarmuka yang sama.

function ThingyWrapper(thingy){ //a function constructor (class-like thingy)

    //thingy is effectively private and persistent for ThingyWrapper instances

    if(typeof thingy === 'array'){
        this.alertItems = function(){
            thingy.forEach(function(el){ alert(el); });
        }
    }
    else {
        this.alertItems = function(){
            for(var x in thingy){ alert(thingy[x]); }
        }
    }
}

function gimmeThingy(){
    var
        coinToss = Math.round( Math.random() ),//gives me 0 or 1
        arrayThingy = [1,2,3],
        objectThingy = { item1:1, item2:2, item3:3 }
    ;

    //0 dynamically evaluates to false in JS
    return new ThingyWrapper( coinToss ? arrayThingy : objectThingy );
}

gimmeThingy().alertItems(); //should be same every time except order of numbers - maybe

Kadang-kadang mungkin masuk akal untuk menghapus berbagai jenis sama sekali tanpa pembungkus generik tetapi jujur ​​dalam 7 tahun tahun menulis JS, itu bukan sesuatu yang saya temukan itu masuk akal atau nyaman untuk dilakukan sangat sering. Sebagian besar itu adalah sesuatu yang akan saya lakukan dalam konteks lingkungan tertutup seperti interior objek di mana segala sesuatunya disatukan. Tapi itu bukan sesuatu yang sudah saya lakukan cukup sering sehingga setiap contoh terlintas dalam pikiran.

Sebagian besar, saya akan menyarankan Anda berhenti memikirkan jenis sama sekali. Anda berurusan dengan tipe saat Anda membutuhkannya dalam bahasa yang dinamis. Tidak lagi. Itu intinya. Jangan periksa jenis setiap argumen tunggal. Itu adalah sesuatu yang saya hanya akan tergoda untuk melakukannya di lingkungan di mana metode yang sama dapat memberikan hasil yang tidak konsisten dengan cara yang tidak jelas (jadi pasti tidak melakukan hal seperti itu). Tapi itu bukan tipe yang penting, tapi bagaimana pun yang kau berikan padaku, bekerja.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.