Parameter Opsional Sedang Berjalan?

464

Bisakah Go memiliki parameter opsional? Atau bisakah saya mendefinisikan dua fungsi dengan nama yang sama dan jumlah argumen yang berbeda?

devyn
sumber
Terkait: ini adalah bagaimana hal itu dapat dilakukan untuk menegakkan parameter wajib ketika menggunakan variadic sebagai parameter opsional: Apakah mungkin untuk memicu kesalahan waktu kompilasi dengan perpustakaan kustom di golang?
icza
11
Google membuat keputusan yang mengerikan, karena terkadang suatu fungsi memiliki kasus penggunaan 90% dan kemudian kasus penggunaan 10%. Arg opsional adalah untuk kasus penggunaan 10% itu. Sane default berarti lebih sedikit kode, lebih sedikit kode berarti lebih banyak rawatan.
Jonathan

Jawaban:

431

Go tidak memiliki parameter opsional dan juga tidak mendukung metode overloading :

Metode pengiriman disederhanakan jika tidak perlu melakukan pencocokan jenis juga. Pengalaman dengan bahasa lain memberi tahu kami bahwa memiliki berbagai metode dengan nama yang sama tetapi tanda tangan yang berbeda kadang-kadang berguna tetapi juga bisa membingungkan dan rapuh dalam praktiknya. Mencocokkan hanya dengan nama dan membutuhkan konsistensi dalam tipe adalah keputusan penyederhanaan utama dalam sistem tipe Go.

Andrew Hare
sumber
58
Apakah makeada kasus khusus? Atau bahkan tidak benar-benar diimplementasikan sebagai fungsi ...
mk12
65
@ Mk12 makeadalah konstruksi bahasa dan aturan yang disebutkan di atas tidak berlaku. Lihat pertanyaan terkait ini .
nemo
7
rangeadalah kasus yang sama dengan make, dalam arti
thiagowfx
14
Method overloads - Ide bagus dalam teori dan sangat baik ketika diimplementasikan dengan baik. Namun saya telah menyaksikan sampah yang terlalu banyak dalam praktik dan karenanya akan setuju dengan keputusan Google
trevorgk
118
Saya akan pergi mengambil risiko dan tidak setuju dengan pilihan ini. Perancang bahasa pada dasarnya mengatakan, "Kita perlu overloading fungsi untuk mendesain bahasa yang kita inginkan, jadi make, range, dan sebagainya pada dasarnya kelebihan beban, tetapi jika Anda ingin overloading fungsi untuk merancang API yang Anda inginkan, yah, itu sulit." Fakta bahwa beberapa programmer menyalahgunakan fitur bahasa bukan argumen untuk menyingkirkan fitur tersebut.
Tom
216

Cara yang bagus untuk mencapai sesuatu seperti parameter opsional adalah dengan menggunakan args variadic. Fungsi sebenarnya menerima sepotong tipe apa pun yang Anda tentukan.

func foo(params ...int) {
    fmt.Println(len(params))
}

func main() {
    foo()
    foo(1)
    foo(1,2,3)
}
Ferguzz
sumber
"Fungsi sebenarnya menerima sepotong tipe apa pun yang Anda tentukan" bagaimana?
Alix Axel
3
dalam contoh di atas, paramsadalah sepotong int
Ferguzz
76
Tetapi hanya untuk jenis params yang sama :(
Juan de Parras
15
@JuandeParras Yah, Anda masih bisa menggunakan sesuatu seperti ... antarmuka {} kurasa.
maufl
5
Dengan ... ketik Anda tidak menyampaikan arti dari opsi individual. Gunakan struct sebagai gantinya. ... type berguna untuk nilai-nilai yang seharusnya Anda harus masukkan dalam array sebelum panggilan.
user3523091
170

Anda dapat menggunakan struct yang mencakup parameter:

type Params struct {
  a, b, c int
}

func doIt(p Params) int {
  return p.a + p.b + p.c 
}

// you can call it without specifying all parameters
doIt(Params{a: 1, c: 9})
deamon
sumber
12
Akan lebih bagus jika struct bisa memiliki nilai default di sini; apa pun yang dihilangkan pengguna secara default ke nilai nol untuk tipe itu, yang mungkin atau mungkin bukan argumen default yang sesuai untuk fungsi tersebut.
jsdw
41
@lytnus, saya benci untuk membagi rambut, tetapi bidang yang nilai-nilainya dihilangkan akan default ke 'nilai nol' untuk tipenya; nihil adalah binatang yang berbeda. Jika jenis bidang yang dihilangkan kebetulan menjadi pointer, nilai nol akan menjadi nol.
burfl
2
@burfl ya, kecuali gagasan "nilai nol" sama sekali tidak berguna untuk tipe int / float / string, karena nilai-nilai itu bermakna dan jadi Anda tidak dapat membedakannya jika nilai dihilangkan dari struct atau jika nilai nol adalah berlalu dengan sengaja.
keymone
3
@ somymone, saya tidak setuju dengan Anda. Saya hanya menjadi pedantic tentang pernyataan di atas bahwa nilai-nilai dihilangkan oleh default pengguna ke "nilai nil untuk tipe itu", yang tidak benar. Mereka default ke nilai nol, yang mungkin atau mungkin tidak nol, tergantung pada apakah jenisnya adalah pointer.
burfl
125

Untuk parameter opsional yang sewenang-wenang dan berpotensi besar, idiom yang bagus adalah menggunakan opsi Fungsional .

Untuk tipe Anda Foobar, tulis pertama hanya satu konstruktor:

func NewFoobar(options ...func(*Foobar) error) (*Foobar, error){
  fb := &Foobar{}
  // ... (write initializations with default values)...
  for _, op := range options{
    err := op(fb)
    if err != nil {
      return nil, err
    }
  }
  return fb, nil
}

di mana setiap opsi adalah fungsi yang mengubah Foobar. Kemudian berikan cara mudah bagi pengguna Anda untuk menggunakan atau membuat opsi standar, misalnya:

func OptionReadonlyFlag(fb *Foobar) error {
  fb.mutable = false
  return nil
}

func OptionTemperature(t Celsius) func(*Foobar) error {
  return func(fb *Foobar) error {
    fb.temperature = t
    return nil
  }
}

Tempat bermain

Untuk keringkasan, Anda dapat memberi nama dengan jenis opsi ( Taman Bermain ):

type OptionFoobar func(*Foobar) error

Jika Anda membutuhkan parameter wajib, tambahkan sebagai argumen pertama konstruktor sebelum variadic options.

Manfaat utama dari idiom opsi Fungsional adalah:

  • API Anda dapat tumbuh dari waktu ke waktu tanpa melanggar kode yang ada, karena tanda tangan konstruktor tetap sama ketika opsi baru diperlukan.
  • itu memungkinkan penggunaan case default menjadi yang paling sederhana: tidak ada argumen sama sekali!
  • ini memberikan kontrol yang baik atas inisialisasi nilai kompleks.

Teknik ini diciptakan oleh Rob Pike dan juga didemonstrasikan oleh Dave Cheney .

Deleplace
sumber
15
Pintar, tapi terlalu rumit. Filosofi Go adalah menulis kode secara langsung. Cukup lewati struct dan uji untuk nilai-nilai default.
user3523091
9
Hanya FYI, penulis asli idiom ini, setidaknya penerbit pertama yang dirujuk, adalah Komandan Rob Pike, yang saya anggap cukup berwibawa untuk filosofi Go. Tautan - commandcenter.blogspot.bg/2014/01/… . Juga mencari "Sederhana itu rumit".
Petar Donchev
2
#JMTCW, tapi saya menemukan pendekatan ini sangat sulit untuk dipikirkan. Saya lebih suka meneruskan dalam nilai-nilai, yang sifat-sifatnya bisa jadi func()jika perlu, daripada itu menekuk otak saya di sekitar pendekatan ini. Setiap kali saya harus menggunakan pendekatan ini, seperti dengan perpustakaan Echo, saya menemukan otak saya terjebak dalam lubang kelinci abstraksi. #fwiw
MikeSchinkel
terima kasih, sedang mencari idiom ini. Bisa juga di sana sebagai jawaban yang diterima.
r --------- k
6

Tidak - tidak juga. Per the Go for C ++ programmer programmer ,

Go tidak mendukung fungsi yang berlebihan dan tidak mendukung operator yang ditentukan pengguna.

Saya tidak dapat menemukan pernyataan yang sama jelasnya bahwa parameter opsional tidak didukung, tetapi mereka juga tidak didukung.

Alex Martelli
sumber
8
"Tidak ada rencana saat ini untuk [parameter opsional]." Ian Lance Taylor, tim bahasa Go. groups.google.com/group/golang-nuts/msg/030e63e7e681fd3e
peterSO
Tidak ada operator yang ditentukan pengguna adalah keputusan yang mengerikan, karena merupakan inti di balik perpustakaan matematika yang apik, seperti produk titik atau produk silang untuk aljabar linier, yang sering digunakan dalam grafik 3D.
Jonathan
4

Anda dapat merangkum ini dengan sangat baik dalam fungsi yang mirip dengan apa yang ada di bawah ini.

package main

import (
        "bufio"
        "fmt"
        "os"
)

func main() {
        fmt.Println(prompt())
}

func prompt(params ...string) string {
        prompt := ": "
        if len(params) > 0 {
                prompt = params[0]
        }
        reader := bufio.NewReader(os.Stdin)
        fmt.Print(prompt)
        text, _ := reader.ReadString('\n')
        return text
}

Dalam contoh ini, prompt secara default memiliki titik dua dan spasi di depannya. . .

: 

. . . namun Anda dapat menimpanya dengan menyediakan parameter ke fungsi prompt.

prompt("Input here -> ")

Ini akan menghasilkan prompt seperti di bawah ini.

Input here ->
Jam Risser
sumber
3

Saya akhirnya menggunakan kombinasi struktur params dan variadic args. Dengan cara ini, saya tidak perlu mengubah antarmuka yang ada yang dikonsumsi oleh beberapa layanan dan layanan saya dapat melewati params tambahan sesuai kebutuhan. Contoh kode di taman bermain golang: https://play.golang.org/p/G668FA97Nu

Adriana
sumber
3

Bahasa Go tidak mendukung metode overloading, tetapi Anda dapat menggunakan argumen variadik seperti parameter opsional, juga Anda dapat menggunakan antarmuka {} sebagai parameter tetapi itu bukan pilihan yang baik.

BaSO4
sumber
2

Saya sedikit terlambat, tetapi jika Anda menyukai antarmuka yang lancar, Anda dapat mendesain setter Anda untuk panggilan berantai seperti ini:

type myType struct {
  s string
  a, b int
}

func New(s string, err *error) *myType {
  if s == "" {
    *err = errors.New(
      "Mandatory argument `s` must not be empty!")
  }
  return &myType{s: s}
}

func (this *myType) setA (a int, err *error) *myType {
  if *err == nil {
    if a == 42 {
      *err = errors.New("42 is not the answer!")
    } else {
      this.a = a
    }
  }
  return this
}

func (this *myType) setB (b int, _ *error) *myType {
  this.b = b
  return this
}

Dan menyebutnya seperti ini:

func main() {
  var err error = nil
  instance :=
    New("hello", &err).
    setA(1, &err).
    setB(2, &err)

  if err != nil {
    fmt.Println("Failed: ", err)
  } else {
    fmt.Println(instance)
  }
}

Ini mirip dengan idiom opsi Fungsional yang disajikan pada jawaban @Rounet dan menikmati manfaat yang sama tetapi memiliki beberapa kelemahan:

  1. Jika kesalahan terjadi, itu tidak akan langsung dibatalkan, jadi, itu akan sedikit kurang efisien jika Anda mengharapkan konstruktor Anda untuk melaporkan kesalahan sering.
  2. Anda harus menghabiskan satu baris mendeklarasikan errvariabel dan mem-nolkannya.

Namun, ada sedikit kemungkinan keuntungan, pemanggilan fungsi jenis ini seharusnya lebih mudah bagi kompiler untuk inline tetapi saya benar-benar bukan spesialis.

VinGarcia
sumber
ini adalah pola pembangun
UmNyobe
2

Anda dapat melewati parameter bernama acak dengan peta.

type varArgs map[string]interface{}

func myFunc(args varArgs) {

    arg1 := "default" // optional default value
    if val, ok := args["arg1"]; ok {
        // value override or other action
        arg1 = val.(string) // runtime panic if wrong type
    }

    arg2 := 123 // optional default value
    if val, ok := args["arg2"]; ok {
        // value override or other action
        arg2 = val.(int) // runtime panic if wrong type
    }

    fmt.Println(arg1, arg2)
}

func Test_test() {
    myFunc(varArgs{"arg1": "value", "arg2": 1234})
}
bangsawan
sumber
Berikut adalah beberapa komentar tentang pendekatan ini: reddit.com/r/golang/comments/546g4z/…
nobar
0

Kemungkinan lain adalah menggunakan struct yang dengan bidang untuk menunjukkan apakah valid. Tipe null dari sql seperti NullString nyaman. Sangat menyenangkan tidak harus mendefinisikan tipe Anda sendiri, tetapi jika Anda membutuhkan tipe data khusus, Anda selalu dapat mengikuti pola yang sama. Saya pikir opsional-ness jelas dari definisi fungsi dan ada kode atau usaha ekstra minimal.

Sebagai contoh:

func Foo(bar string, baz sql.NullString){
  if !baz.Valid {
        baz.String = "defaultValue"
  }
  // the rest of the implementation
}
pengguna2133814
sumber