พารามิเตอร์เพิ่มเติมใน Go?


464

สามารถมีพารามิเตอร์เสริมได้หรือไม่ หรือฉันสามารถกำหนดฟังก์ชั่นสองอย่างที่มีชื่อเดียวกันและจำนวนอาร์กิวเมนต์ต่างกันได้หรือไม่


ที่เกี่ยวข้อง: นี่คือวิธีที่สามารถทำได้เพื่อบังคับใช้พารามิเตอร์บังคับเมื่อใช้ Variadic เป็นพารามิเตอร์ทางเลือก: เป็นไปได้หรือไม่ที่จะทริกเกอร์ข้อผิดพลาดเวลาคอมไพล์กับไลบรารีที่กำหนดเองใน golang?
icza

11
Google ทำการตัดสินใจที่แย่มากเพราะบางครั้งฟังก์ชั่นนั้นมีกรณีการใช้ 90% และกรณีการใช้งาน 10% arg ตัวเลือกสำหรับกรณีการใช้งาน 10% เริ่มต้นที่มีสติหมายถึงรหัสน้อยลงรหัสน้อยหมายถึงการบำรุงรักษามากขึ้น
Jonathan

คำตอบ:


431

Go ไม่มีพารามิเตอร์ที่เป็นทางเลือกและไม่รองรับการโอเวอร์โหลดวิธี :

วิธีการจัดส่งจะง่ายขึ้นถ้าไม่จำเป็นต้องทำการจับคู่ประเภทเช่นกัน ประสบการณ์กับภาษาอื่น ๆ บอกเราว่าการใช้วิธีการที่หลากหลายด้วยชื่อเดียวกัน แต่ลายเซ็นที่แตกต่างกันนั้นมีประโยชน์ในบางครั้ง แต่อาจทำให้สับสนและเปราะบางในทางปฏิบัติ การจับคู่ตามชื่อเท่านั้นและต้องการความสอดคล้องในประเภทคือการตัดสินใจที่ง่ายขึ้นที่สำคัญในระบบประเภทของ Go


58
เป็นmakeกรณีพิเศษแล้ว? หรือว่ามันไม่ได้ถูกนำมาใช้เป็นฟังก์ชั่นจริงๆ ...
mk12

65
@ Mk12 makeเป็นโครงสร้างภาษาและไม่มีการใช้กฎที่กล่าวถึงข้างต้น ดูคำถามที่เกี่ยวข้องนี้
nemo

7
rangeเป็นกรณีเดียวกับmakeในความรู้สึกว่า
thiagowfx

14
Method overloads - ความคิดที่ดีในทางทฤษฎีและยอดเยี่ยมเมื่อใช้งานได้ดี อย่างไรก็ตามฉันได้เห็นการบรรทุกเกินพิกัดที่ไม่สามารถถอดรหัสได้ในทางปฏิบัติและจะเห็นด้วยกับการตัดสินใจของ Google
trevorgk

118
ฉันจะออกไปที่กิ่งและไม่เห็นด้วยกับตัวเลือกนี้ นักออกแบบภาษาพูดโดยทั่วไปแล้วว่า "เราต้องการฟังก์ชั่นมากไปสำหรับการออกแบบภาษาที่เราต้องการดังนั้นสร้างช่วงและอื่น ๆ เป็นจำนวนมาก แต่ถ้าคุณต้องการให้ฟังก์ชั่นการทำงานหนักเกินไปในการออกแบบ API ที่คุณต้องการ ความจริงที่ว่าโปรแกรมเมอร์บางคนใช้ภาษาในทางที่ผิดไม่ใช่ข้อโต้แย้งในการกำจัดคุณลักษณะดังกล่าว
Tom

216

วิธีที่ดีในการทำสิ่งต่าง ๆ เช่นพารามิเตอร์ทางเลือกคือการใช้ตัวแปรแบบ Arad ฟังก์ชั่นได้รับชิ้นส่วนของประเภทที่คุณระบุ

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

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

"ฟังก์ชั่นจริงได้รับชิ้นส่วนใด ๆ ก็ตามที่คุณระบุ" ได้อย่างไร
Alix Axel

3
ในตัวอย่างด้านบนparamsเป็นส่วนหนึ่งของ ints
Ferguzz

76
แต่สำหรับ params ประเภทเดียวกันเท่านั้น :(
Juan de Parras

15
@JuandeParras เอาล่ะคุณยังสามารถใช้บางอย่างเช่น ... ส่วนต่อประสาน {} ฉันเดา
maufl

5
ด้วย ... ประเภทคุณไม่ได้สื่อความหมายของตัวเลือกแต่ละตัว ใช้ struct แทน ... type มีประโยชน์สำหรับค่าที่คุณจะต้องใส่ในอาร์เรย์ก่อนการโทร
user3523091

170

คุณสามารถใช้ struct ซึ่งรวมถึงพารามิเตอร์:

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
มันจะดีถ้า structs อาจมีค่าเริ่มต้นที่นี่ สิ่งใดที่ผู้ใช้ละเว้นจะมีการกำหนดค่าเริ่มต้นให้เป็นค่าศูนย์สำหรับประเภทนั้นซึ่งอาจหรืออาจไม่ใช่อาร์กิวเมนต์เริ่มต้นที่เหมาะสมสำหรับฟังก์ชัน
jsdw

41
@lytnus ฉันเกลียดที่จะแยกขน แต่ฟิลด์ที่ละเว้นค่าจะเป็นค่าเริ่มต้นเป็น 'ศูนย์ค่า' สำหรับประเภทของพวกเขา; ไม่มีเป็นสัตว์อื่น หากชนิดของเขตข้อมูลที่ถูกละเว้นเกิดขึ้นเป็นตัวชี้ค่าศูนย์จะเป็นศูนย์
burfl

2
@ burfl yeah ยกเว้นความเห็นของ "zero value" ไม่มีประโยชน์อย่างแน่นอนสำหรับ int / float / string ประเภทเพราะค่าเหล่านั้นมีความหมายและคุณไม่สามารถบอกความแตกต่างได้ถ้าค่านั้นถูกตัดออกจาก struct หรือถ้าค่าศูนย์เป็น ผ่านไปโดยเจตนา
keymone

3
@keymone ฉันไม่เห็นด้วยกับคุณ ฉันแค่อวดความรู้เกี่ยวกับข้อความข้างต้นว่าค่าที่ถูกละเว้นโดยค่าเริ่มต้นของผู้ใช้เป็น "ค่าศูนย์สำหรับประเภทนั้น" ซึ่งไม่ถูกต้อง พวกเขาเริ่มต้นให้เป็นค่าศูนย์ซึ่งอาจหรือไม่อาจเป็นศูนย์ขึ้นอยู่กับว่าประเภทเป็นตัวชี้
burfl

124

สำหรับพลจำนวนมากที่อาจเกิดขึ้นของพารามิเตอร์ที่ไม่จำเป็นเป็นสำนวนที่ดีคือการใช้ตัวเลือกฟังก์ชั่น

สำหรับประเภทของคุณFoobarแรกเขียนเพียงหนึ่งคอนสตรัค:

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
}

โดยที่แต่ละตัวเลือกคือฟังก์ชั่นที่ทำให้เกิดการเปลี่ยนแปลง Foobar จากนั้นให้วิธีที่สะดวกสำหรับผู้ใช้ของคุณในการใช้หรือสร้างตัวเลือกมาตรฐานเช่น:

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

สนามเด็กเล่น

เพื่อความกระชับคุณอาจตั้งชื่อให้กับประเภทของตัวเลือก ( สนามเด็กเล่น ):

type OptionFoobar func(*Foobar) error

หากคุณต้องการพารามิเตอร์บังคับให้เพิ่มเป็นอาร์กิวเมนต์แรกของการสร้างก่อน optionsvariadic

ประโยชน์หลักของสำนวนฟังก์ชั่นคือ:

  • API ของคุณสามารถเติบโตได้ตลอดเวลาโดยไม่ทำลายโค้ดที่มีอยู่เพราะลายเซ็น constuctor ยังคงเหมือนเดิมเมื่อต้องการตัวเลือกใหม่
  • มันช่วยให้กรณีการใช้งานเริ่มต้นจะง่ายที่สุด: ไม่มีข้อโต้แย้งเลย!
  • มันให้การควบคุมที่ดีในการเริ่มต้นของค่าที่ซับซ้อน

เทคนิคนี้จะได้รับการประกาศเกียรติคุณจากร็อบหอกและยังแสดงให้เห็นโดยเดฟเชนีย์



15
ฉลาด แต่ซับซ้อนเกินไป ปรัชญาของ Go คือการเขียนโค้ดอย่างตรงไปตรงมา เพียงผ่านโครงสร้างและทดสอบค่าเริ่มต้น
user3523091

9
เพียงแค่ FYI ผู้เขียนต้นฉบับของสำนวนนี้อย่างน้อยที่สุดผู้โฆษณารายแรกที่อ้างอิงคือ Commander Rob Pike ซึ่งฉันคิดว่ามีอำนาจเพียงพอสำหรับปรัชญา Go Link - commandcenter.blogspot.bg/2014/01/... ค้นหาคำว่า "Simple is ซับซ้อน"
Petar Donchev

2
#JMTCW แต่ฉันพบว่าวิธีนี้ยากที่จะให้เหตุผล ฉันอยากจะผ่านโครงสร้างของค่าที่มีคุณสมบัติอาจเป็นfunc()ถ้าจำเป็นกว่าที่โค้งสมองของฉันรอบวิธีนี้ เมื่อใดก็ตามที่ฉันต้องใช้วิธีการนี้เช่นห้องสมุด Echo ฉันพบว่าสมองของฉันติดอยู่ในรูกระต่ายของนามธรรม #fwiw
MikeSchinkel

ขอบคุณกำลังมองหาสำนวนนี้ อาจจะอยู่ที่นั่นเป็นคำตอบที่ยอมรับได้
r --------- k


6

ไม่ - ไม่ ต่อไปสำหรับ C ++ โปรแกรมเมอร์เอกสาร

Go ไม่สนับสนุนการโอเวอร์โหลดฟังก์ชั่นและไม่รองรับโอเปอเรเตอร์ที่ผู้ใช้กำหนด

ฉันไม่พบคำสั่งที่ชัดเจนพอ ๆ กันว่าไม่สนับสนุนพารามิเตอร์ที่เป็นทางเลือก แต่ไม่สนับสนุนเช่นกัน


8
"ไม่มีแผนปัจจุบันสำหรับ [พารามิเตอร์ทางเลือก]" Ian Lance Taylor, ทีมงาน Go groups.google.com/group/golang-nuts/msg/030e63e7e681fd3e
peterSO

ไม่มีตัวดำเนินการที่ผู้ใช้กำหนดเองเป็นการตัดสินใจที่น่ากลัวเนื่องจากเป็นแกนหลักของไลบรารีคณิตศาสตร์ที่ลื่นไหลเช่นจุดผลิตภัณฑ์หรือผลิตภัณฑ์ข้ามสำหรับพีชคณิตเชิงเส้นซึ่งมักใช้ในกราฟิกสามมิติ
Jonathan

4

คุณสามารถสรุปแค็ปซูลนี้ได้เป็นอย่างดีใน func คล้ายกับที่อยู่ด้านล่าง

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
}

ในตัวอย่างนี้พรอมต์โดยค่าเริ่มต้นจะมีเครื่องหมายโคลอนและช่องว่างด้านหน้า . .

: 

. . . อย่างไรก็ตามคุณสามารถแทนที่ได้โดยการจัดหาพารามิเตอร์ให้กับฟังก์ชั่นพรอมต์

prompt("Input here -> ")

สิ่งนี้จะส่งผลให้มีการแจ้งเตือนแบบด้านล่าง

Input here ->

3

ฉันลงเอยด้วยการใช้โครงสร้างของ params และ arad variadic ด้วยวิธีนี้ฉันไม่จำเป็นต้องเปลี่ยนอินเทอร์เฟซที่มีอยู่ซึ่งถูกใช้โดยบริการหลายอย่างและบริการของฉันก็สามารถส่งผ่านพารามิเตอร์เพิ่มเติมได้ตามต้องการ โค้ดตัวอย่างในสนามเด็กเล่น golang: https://play.golang.org/p/G668FA97Nu


3

ภาษาโกไม่สนับสนุนการใช้วิธีการมากไป แต่คุณสามารถใช้ตัวแปรแบบหลากหลายเช่นเดียวกับพารามิเตอร์ทางเลือกคุณยังสามารถใช้อินเตอร์เฟส {} เป็นพารามิเตอร์ได้ แต่ไม่ใช่ตัวเลือกที่ดี


2

ฉันช้าไปหน่อย แต่ถ้าคุณชอบส่วนต่อประสานที่คล่องแคล่วคุณอาจออกแบบ setters ของคุณสำหรับการโทรที่ถูกล่ามโซ่เช่นนี้:

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
}

แล้วเรียกมันว่าสิ่งนี้:

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

สิ่งนี้คล้ายกับสำนวนการใช้งานฟังก์ชั่นที่มีอยู่ใน @Ripounet และได้รับประโยชน์เหมือนกัน แต่มีข้อเสีย:

  1. หากมีข้อผิดพลาดเกิดขึ้นจะไม่ยกเลิกทันทีดังนั้นจะมีประสิทธิภาพน้อยกว่าเล็กน้อยหากคุณคาดว่าตัวสร้างของคุณจะรายงานข้อผิดพลาดบ่อยครั้ง
  2. คุณจะต้องใช้บรรทัดเพื่อประกาศerrตัวแปรและ zeroing

อย่างไรก็ตามมีข้อได้เปรียบเล็กน้อยที่เป็นไปได้การเรียกใช้ฟังก์ชั่นประเภทนี้น่าจะง่ายกว่าสำหรับคอมไพเลอร์ที่จะแทรกเข้ามา แต่ฉันไม่ใช่ผู้เชี่ยวชาญ


นี่คือรูปแบบการสร้าง
UmNyobe

2

คุณสามารถส่งพารามิเตอร์ที่กำหนดชื่อเองได้โดยใช้แผนที่

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

นี่คือความเห็นบางส่วนเกี่ยวกับวิธีการนี้: reddit.com/r/golang/comments/546g4z/…
Nobar


0

ความเป็นไปได้อีกอย่างก็คือการใช้โครงสร้างที่มีเขตข้อมูลเพื่อระบุว่ามันถูกต้องหรือไม่ ประเภท null จาก sql เช่นNullStringสะดวก เป็นการดีที่ไม่จำเป็นต้องกำหนดประเภทของคุณเอง แต่ในกรณีที่คุณต้องการชนิดข้อมูลที่กำหนดเองคุณสามารถทำตามรูปแบบเดียวกันได้เสมอ ฉันคิดว่าตัวเลือก ness นั้นชัดเจนจากนิยามฟังก์ชันและมีโค้ดหรือความพยายามน้อยมาก

ตัวอย่างเช่น:

func Foo(bar string, baz sql.NullString){
  if !baz.Valid {
        baz.String = "defaultValue"
  }
  // the rest of the implementation
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.