Go中的可选参数?


464

Go可以有可选参数吗?还是可以只定义两个具有相同名称和不同数量参数的函数?


相关:这是在使用可变参数作为可选参数时强制执行强制参数的方法:使用golang中的自定义库是否可以触发编译时错误?
icza

11
Google做出了一个糟糕的决定,因为有时某个功能有90%的用例,然后有10%的用例。可选arg用于该10%的用例。Sane默认设置意味着更少的代码,更少的代码意味着更多的可维护性。
乔纳森

Answers:


431

Go没有可选参数,也不支持方法重载

如果方法分派也不需要进行类型匹配,则可以简化方法分派。其他语言的经验告诉我们,使用具有相同名称但签名不同的多种方法有时会很有用,但在实践中也可能会造成混淆和脆弱。在Go的类型系统中,仅按名称进行匹配并要求类型一致是一个简化的主要决定。


58
那是make特例吗?还是不是真正实现为一个功能……
mk12 2013年

65
@ Mk12 make是一种语言构造,上面提到的规则不适用。请参阅此相关问题
nemo 2013年

7
rangemake从这个意义上讲,情况是一样的
thiagowfx 2014年

14
方法重载-理论上的好主意,如果实施得当,则非常好。但是我在实践中目睹了垃圾难以理解的超载,因此同意Google的决定
trevorgk 2014年

118
我将不知所措,不同意这种选择。语言设计人员基本上说过:“我们需要函数重载来设计所需的语言,因此make,range等实际上都是重载的,但是如果您想函数重载来设计所需的API,那很难。” 一些程序员滥用语言功能的事实并不是放弃该功能的依据。
汤姆(Tom)

216

一种实现类似可选参数的好方法是使用可变参数。该函数实际上会接收您指定的任何类型的切片。

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

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

“函数实际上会收到您指定的任何类型的切片”怎么办?
Alix Axel 2014年

3
在上面的示例中,params是一个整数切片
Ferguzz 2014年

76
但仅适用于相同类型的参数:(
Juan de Parras 2014年

15
@JuandeParras好吧,我猜您仍然可以使用... interface {}之类的东西。
maufl 2015年

5
使用... type不能传达单个选项的含义。改用结构。... type对于在调用之前必须将其放入数组中的值非常方便。
user3523091 2016年

169

您可以使用包含以下参数的结构:

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
如果结构可以在此处具有默认值,那就太好了;用户忽略的任何内容均默认为该类型的nil值,该值可能是也可能不是该函数的适当默认参数。
jsdw 2013年

41
@lytnus,我不喜欢拆分头发,但是省略值的字段的类型默认为“零值”;nil是另一种动物。如果省略字段的类型恰好是指针,则零值为nil。
burfl 2014年

2
@burfl是的,除了“零值”的概念对于int / float / string类型是绝对没有用的,因为这些值是有意义的,因此您无法分辨出是否从结构中省略了该值或是否为零。有意通过。
按键

3
@keymone,我不同意你的看法。我只是对上面的陈述而之以鼻,该陈述是用户忽略的值默认为“该类型的零值”,这是不正确的。它们默认为零值(取决于类型是否为指针),该值可以为nil,也可以为nil。
Burfl

123

对于任意的,可能大量的可选参数,一个好习惯是使用Functional options

对于您的type 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
  }
}

操场

为简洁起见,您可以为选项的类型命名(Playground):

type OptionFoobar func(*Foobar) error

如果需要强制性参数,请将它们作为构造函数的第一个参数添加到variadic之前options

功能选项成语的主要优点是:

  • 您的API可以随着时间的增长而不会破坏现有代码,因为当需要新的选项时,构造器签名保持不变。
  • 它使默认用例变得最简单:根本没有参数!
  • 它提供了对复杂值的初始化的精细控制。

该技术由Rob Pike创造,也由Dave Cheney演示。



15
聪明,但是太复杂了。Go的理念是以一种简单的方式编写代码。只需通过一个结构并测试默认值即可。
user3523091 2016年

9
仅供参考,这个成语的原始作者,至少是第一个被引用的出版商,是Commander Rob Pike,我认为他对Go哲学具有足够的权威。链接-commandcenter.blogspot.bg/2014/01/…。还要搜索“简单就是复杂”。
彼得·唐切夫

2
#JMTCW,但我发现这种方法很难推论。我宁愿传递一个值的结构(func()如果需要的话其属性可以是s),而不是让我的脑子绕这种方法。每当我必须使用这种方法(例如使用Echo库)时,我都会发现自己的大脑陷入了抽象的兔子洞。#fwiw
MikeSchinkel

谢谢,一直在寻找这个习语。也可以将其作为公认的答案。
r --------- k


6

不-都不。根据Go for C ++程序员文档,

Go不支持函数重载,也不支持用户定义的运算符。

我找不到一个同样明确的说法,即不支持可选参数,但也不支持它们。


8
“此[可选参数]目前没有计划。” Ian Lance Taylor,Go语言团队。groups.google.com/group/golang-nuts/msg/030e63e7e681fd3e
peterSO 2010年

没有用户定义的运算符是一个糟糕的决定,因为它是任何光滑的数学库(例如通常用于3D图形中的线性代数的点积或叉积)背后的核心。
乔纳森

4

您可以将其很好地封装在类似于下面的函数中。

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

Go语言不支持方法重载,但是您可以像使用可选参数一样使用可变参数args,也可以使用interface {}作为参数,但这不是一个好选择。


2

我来晚了一点,但是如果您喜欢流畅的界面,可以像下面这样设计链式呼叫的设置器:

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变量并将其清零。

但是,这可能会有一个小的优势,这种类型的函数调用应该更易于编译器内联,但我并不是专家。


这是一个构建器模式
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

另一种可能性是使用带有字段的struct来指示其是否有效。sql中的空类型,例如NullString很方便。不必定义自己的类型很高兴,但是如果您需要自定义数据类型,则可以始终遵循相同的模式。我认为从函数定义中可以清楚地看出可选性,并且几乎没有额外的代码或工作。

举个例子:

func Foo(bar string, baz sql.NullString){
  if !baz.Valid {
        baz.String = "defaultValue"
  }
  // the rest of the implementation
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.