Go İsteğe Bağlı Parametreler?


464

Go'nun isteğe bağlı parametreleri olabilir mi? Yoksa aynı ada ve farklı sayıda bağımsız değişkene sahip iki işlev tanımlayabilir miyim?


İlgili: isteğe bağlı parametreler olarak varyantik
icza

11
Google korkunç bir karar verdi, çünkü bazen bir işlevin% 90'lık bir kullanım durumu ve daha sonra% 10'luk bir kullanım durumu vardır. İsteğe bağlı arg,% 10'luk kullanım durumu içindir. Sane varsayılanları daha az kod, daha az kod daha fazla sürdürülebilirlik anlamına gelir.
Jonathan

Yanıtlar:


431

Go'nun isteğe bağlı parametreleri yoktur veya yöntem aşırı yüklenmesini desteklemez :

Tür eşleştirme yapması gerekmiyorsa yöntem dağıtımı basitleştirilir. Diğer dillerle ilgili deneyimler bize, aynı ada ancak farklı imzalara sahip çeşitli yöntemlere sahip olmanın bazen yararlı olduğunu ancak pratikte kafa karıştırıcı ve kırılgan olabileceğini söyledi. Yalnızca isimle eşleştirmek ve türlerde tutarlılık gerektirmek Go'nun tür sisteminde basitleştirici bir karardı.


58
Mi make, sonra özel bir durum? Yoksa gerçekten bir işlev olarak uygulanmadı mı…
mk12

65
@ Mk12 makebir dil yapısıdır ve yukarıda belirtilen kurallar geçerli değildir. Bu ilgili soruya bakın .
nemo

7
rangemakebu anlamda aynı durumdur
thiagowfx

14
Yöntem aşırı yüklenmeleri - Teoride harika bir fikir ve iyi uygulandığında mükemmel. Ancak pratikte çöplerin çözülemez aşırı yüklenmesine tanık oldum ve bu nedenle Google'ın kararına katılıyorum
trevorgk

119
Bir uzuv çıkacağım ve bu seçeneğe katılmayacağım. Dil tasarımcıları temel olarak, "İstediğimiz dili tasarlamak için işlev aşırı yüklemesine ihtiyacımız var, bu yüzden yapmak, menzil vb. Bazı programcıların bir dil özelliğini kötüye kullanması, bu özellikten kurtulmak için bir argüman değildir.
Tom

217

İsteğe bağlı parametreler gibi bir şey elde etmenin güzel bir yolu varyasyon değişkenlerini kullanmaktır. İşlev aslında belirttiğiniz türden bir dilim alır.

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

func main() {
    foo()
    foo(1)
    foo(1,2,3)
}

"işlev aslında belirttiğiniz türden bir dilim alır" nasıl?
Alix Axel

3
Yukarıdaki örnekte, paramsbir dilim ints
Ferguzz

76
Ama sadece aynı tür paramlar için :(
Juan de Parras

15
@JuandeParras Eh, hala ... arayüzü {} gibi bir şey kullanabilirsiniz sanırım.
maufl

5
... tipi ile bireysel seçeneklerin anlamını aktarmazsınız. Bunun yerine bir yapı kullanın. ... type, aramadan önce bir diziye koymanız gereken değerler için kullanışlıdır.
user3523091

170

Parametreleri içeren bir yapı kullanabilirsiniz:

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})

12
Burada yapıların varsayılan değerlere sahip olması harika olurdu; kullanıcının atladığı her şey varsayılan olarak bu tür için nil değerine ayarlanır; bu, işleve uygun bir varsayılan bağımsız değişken olabilir veya olmayabilir.
jsdw

41
@lytnus, kılları bölmekten nefret ederim, ancak değerlerin atlandığı alanlar varsayılan olarak türleri için 'sıfır değerine' ayarlanacaktır; Nil farklı bir hayvandır. Atlanan alanın türü bir işaretçi olursa, sıfır değeri sıfır olur.
burfl

2
@burfl evet, "sıfır değeri" kavramı int / float / string türleri için kesinlikle işe yaramaz, çünkü bu değerler anlamlıdır ve bu nedenle değerin yapıdan çıkarılmış olması veya sıfır değerinin kasıtlı olarak geçti.
keymone

3
@keymone, sana katılmıyorum. Ben sadece kullanıcı varsayılan tarafından yanlış "bu tür için nil değeri" atlanmış değerleri yukarıdaki ifade hakkında bilgiç olmak. Varsayılan olarak, türün bir işaretçi olup olmamasına bağlı olarak sıfır olabilecek veya olmayabilir sıfır değerine ayarlanır.
burfl

125

İsteğe bağlı olarak çok sayıda isteğe bağlı parametre için, hoş bir deyim İşlevsel seçenekleri kullanmaktır .

Türünüz için Foobar, önce yalnızca bir kurucu yazın:

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
}

burada her seçenek Foobar'ı değiştiren bir işlevdir. Ardından, kullanıcınızın standart seçenekleri kullanması veya oluşturması için uygun yollar sağlayın, örneğin:

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
  }
}

Oyun alanı

Kısaca, seçeneklerin türüne bir ad verebilirsiniz ( Oyun Alanı ):

type OptionFoobar func(*Foobar) error

Zorunlu parametrelere ihtiyacınız varsa, varyasyondan önce kurucunun ilk argümanları olarak ekleyin options.

İşlevsel seçenekler deyiminin temel faydaları şunlardır:

  • yeni seçenek gerektiğinde constuctor imzası aynı kaldığından API'nız mevcut kodu kırmadan zaman içinde büyüyebilir.
  • varsayılan kullanım durumunun en basit olmasını sağlar: hiç argüman yok!
  • karmaşık değerlerin başlatılması üzerinde ince kontrol sağlar.

Bu teknik Rob Pike tarafından üretildi ve Dave Cheney tarafından da gösterildi .



15
Zeki, ama çok karmaşık. Go'nun felsefesi, kodu açık bir şekilde yazmaktır. Sadece bir yapı geçirin ve varsayılan değerleri test edin.
user3523091

9
Bu deyimin orijinal yazarı olan ve en azından ilk yayımlanan yayıncı olan FYI, Go felsefesi için yeterince yetkili olduğunu düşündüğüm Komutan Rob Pike. Bağlantı - commandcenter.blogspot.bg/2014/01/… . Ayrıca "Simple is karmaşık" için arama.
Petar Donchev

2
#JMTCW, ama bu yaklaşımı akıl yürütmek çok zor buluyorum. Beynimi func()bu yaklaşımın etrafında bükmektense , özellikleri gerekirse olması gereken bir değerler yapısını iletmeyi tercih ederim . Yankı kütüphanesinde olduğu gibi bu yaklaşımı kullanmak zorunda kaldığımda, beynimin soyutlamaların tavşan deliğine sıkıştığını görüyorum. #fwiw
MikeSchinkel

teşekkürler, bu deyimi arıyordu. Kabul edilebilir cevap olarak orada olabilir.
r --------- k


6

Hayır - hiçbiri. Başına C ++ programcıları için Go docs,

Go, işlev aşırı yüklenmesini desteklemez ve kullanıcı tanımlı işleçleri desteklemez.

İsteğe bağlı parametrelerin desteklenmediği gibi eşit derecede açık bir ifade bulamıyorum, ancak desteklenmiyor.


8
Msgstr "Bu [isteğe bağlı parametreler] için geçerli bir plan yok." Ian Lance Taylor, Go dil ekibi. groups.google.com/group/golang-nuts/msg/030e63e7e681fd3e
peterSO

Kullanıcı tanımlı hiçbir operatör korkunç bir karardır, çünkü genellikle 3D grafiklerde kullanılan nokta ürünler veya doğrusal cebir için çapraz ürünler gibi kaygan matematik kütüphanelerinin arkasındaki çekirdektir.
Jonathan

4

Bunu, aşağıdakine benzer bir fonkta oldukça güzel bir şekilde kapsülleyebilirsiniz.

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
}

Bu örnekte, istemde varsayılan olarak iki nokta üst üste ve önünde bir boşluk vardır. . .

: 

. . . ancak bilgi istemi işlevine bir parametre sağlayarak bunu geçersiz kılabilirsiniz.

prompt("Input here -> ")

Bu, aşağıdaki gibi bir istemle sonuçlanacaktır.

Input here ->

3

Sonunda params ve varyantik argümanların bir yapısını kullandım. Bu şekilde, birkaç servis tarafından tüketilen mevcut arayüzü değiştirmek zorunda kalmadım ve servisim gerektiğinde ek parametreler geçebildi. Golang oyun alanındaki örnek kod: https://play.golang.org/p/G668FA97Nu


3

Git dili yöntem aşırı yüklemesini desteklemez, ancak isteğe bağlı parametreler gibi değişken değişkenleri de kullanabilirsiniz, ayrıca parametre olarak {} arabirimini de kullanabilirsiniz, ancak iyi bir seçim değildir.


2

Biraz geç kaldım, ancak akıcı arayüzden hoşlanıyorsanız ayarlayıcılarınızı aşağıdaki gibi zincirleme çağrılar için tasarlayabilirsiniz:

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
}

Ve sonra şöyle deyin:

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)
  }
}

Bu, @Ripounet yanıtında sunulan İşlevsel seçenekler deyimine benzer ve aynı avantajlardan yararlanır, ancak bazı dezavantajları vardır:

  1. Bir hata oluşursa hemen iptal edilmez, bu nedenle kurucunuzun hataları sık sık bildirmesini beklerseniz biraz daha az verimli olur.
  2. Bir errdeğişkeni bildirip sıfırlamak için bir satır harcamanız gerekir.

Bununla birlikte, olası küçük bir avantaj vardır, bu tür işlev çağrıları derleyicinin satır içi için daha kolay olmalıdır, ancak gerçekten bir uzman değilim.


Bu bir inşaatçı desen
UmNyobe

2

Bir harita ile rastgele adlandırılmış parametreleri iletebilirsiniz.

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})
}

İşte bu yaklaşımla ilgili bazı yorumlar: reddit.com/r/golang/comments/546g4z/…
nobar


0

Başka bir olasılık, geçerli olup olmadığını belirtmek için alanlı bir yapı kullanmak olacaktır. NullString gibi sql'den null türleri uygundur. Kendi türünüzü tanımlamak güzel, ancak özel bir veri türüne ihtiyacınız varsa her zaman aynı kalıbı takip edebilirsiniz. Ben isteğe bağlı-ness fonksiyon tanımından açık olduğunu ve minimum ekstra kod veya çaba olduğunu düşünüyorum.

Örnek olarak:

func Foo(bar string, baz sql.NullString){
  if !baz.Valid {
        baz.String = "defaultValue"
  }
  // the rest of the implementation
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.