Goのオプションパラメータ?


464

Goにオプションのパラメーターを指定できますか?または、同じ名前で引数の数が異なる2つの関数を定義できますか?


関連:これは、可変パラメーターを可変パラメーターとして使用するときに必須パラメーターを強制する方法です:golangのカスタムライブラリでコンパイル時エラーをトリガーすることは可能ですか?
icza

11
関数には90%のユースケースがあり、次に10%のユースケースがある場合があるため、Googleはひどい決定をしました。オプションの引数は、その10%のユースケース用です。健全なデフォルトとは、コードが少ないことを意味し、コードが少ないことは保守性が高いことを意味します。
ジョナサン

回答:


431

Goにはオプションのパラメーターがなく、メソッドのオーバーロードもサポートされていません。

タイプマッチングも必要としない場合、メソッドディスパッチは簡略化されます。他の言語を使用した経験から、同じ名前でシグネチャが異なるさまざまなメソッドを使用すると便利な場合がありますが、実際には混乱しやすく、壊れやすいこともあります。名前のみで照合し、型の一貫性を要求することは、Goの型システムにおける主要な簡素化の決定でした。


58
make、その後、特殊なケース?それとも、実際には関数として実装されていませんか
mk12

65
@ Mk12 makeは言語構成要素であり、上記のルールは適用されません。この関連質問を参照してください。
nemo 2013年

7
rangemakeその意味ではと同じケースです
thiagowfx 2014

14
メソッドのオーバーロード-理論的には優れたアイデアであり、適切に実装すると優れたものになります。しかし、私は実際に判読不能なゴミのオーバーロードを目撃したため、Googleの決定に同意します
trevorgk 2014年

118
私は手足を出し、この選択に同意しません。言語設計者は基本的に、「必要な言語を設計するには関数のオーバーロードが必要なため、本質的にオーバーロードが必要ですが、必要なAPIを設計するために関数のオーバーロードが必要な場合は、それは難しいです。」一部のプログラマーが言語機能を誤用しているという事実は、その機能を取り除くための議論ではありません。
トム

216

オプションのパラメーターのようなものを実現する良い方法は、可変個引数を使用することです。関数は実際に、指定したタイプのスライスを受け取ります。

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

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

「関数は実際に指定したタイプのスライスを受け取ります」どのようにですか?
Alix Axel 14

3
上記の例でparamsは、intのスライスです
Ferguzz

76
ただし、同じタイプの
パラメータ

15
@JuandeParrasええと、まだ... interface {}のようなものを使用できます。
maufl

5
... typeでは、個々のオプションの意味を伝えていません。代わりに構造体を使用してください。... typeは、呼び出し前に配列に配置する必要がある値に便利です。
user3523091

170

パラメータを含む構造体を使用できます:

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型にはまったく意味がありません。これらの値は意味があり、構造体から値が省略されたか、ゼロ値が意図的に通過した。
keymone 2018年

3
@keymone、私はあなたに反対しません。ユーザーがデフォルトで省略した値は「そのタイプのnil値」にデフォルトで設定されているという上記の記述については、私は単に平凡でしたが、これは誤りです。それらはデフォルトでゼロ値に設定され、型がポインターかどうかに応じて、nilである場合とそうでない場合があります。
burfl

124

任意の、場合によっては多数のオプションパラメータの場合、便利なイディオムは、機能オプションを使用することです。

タイプにはFoobar、最初にコンストラクタを1つだけ記述します。

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

必須パラメーターが必要な場合は、可変引数の前にコンストラクターの最初の引数として追加しますoptions

機能オプションイディオムの主な利点は次のとおりです。

  • 新しいオプションが必要な場合、コンストラクターのシグニチャーは同じままであるため、APIは既存のコードを壊すことなく、時間の経過とともに大きくなる可能性があります。
  • これにより、デフォルトのユースケースが最もシンプルになります。引数はありません!
  • 複雑な値の初期化を細かく制御できます。

この技術はによって鋳造されたロブ・パイクともによって実証さデイブチェイニー



15
巧妙ですが、複雑すぎます。Goの哲学は、簡単な方法でコードを書くことです。構造体を渡して、デフォルト値をテストするだけです。
user3523091 2016年

9
このイディオムの最初の作者、少なくとも最初に言及された出版社であるFYIは、私がGo哲学に対して十分に権威があると考えるロブパイク司令官です。リンク-commandcenter.blogspot.bg/2014/01/…。「シンプルは複雑」も検索してください。
Petar Donchev 2016年

2
#JMTCW、しかし私はこのアプローチを考えるのは非常に難しいと思います。値の構造体を渡すほうがはるかに望ましいです。そのプロパティはfunc()、必要に応じてsにすることもできますが、このアプローチでは頭が曲がります。Echoライブラリーなどでこのアプローチを使用しなければならないときはいつでも、抽象化のうさぎの穴に頭が悩まされています。#fwiw
MikeSchinkel

おかげで、このイディオムを探していました。受け入れられた答えと同じくらいそこにいることもできます。
r --------- k


6

いいえ、どちらでもありません。パーC ++プログラマのために行くドキュメント、

Goは関数のオーバーロードをサポートしておらず、ユーザー定義の演算子もサポートしていません。

オプションのパラメーターがサポートされていないという同様に明確なステートメントを見つけることはできませんが、それらもサポートされていません。


8
「この[オプションパラメータ]の最新計画はありません。」イアンランステイラー、Go言語チーム。groups.google.com/group/golang-nuts/msg/030e63e7e681fd3e
peterSO

3Dグラフィックスでよく使用される線形代数のドット積やクロス積などの滑らかな数学ライブラリの中心となるため、ユーザー定義の演算子はありません。
ジョナサン

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とvariadic argsの構造の組み合わせを使用してしまいました。このようにして、いくつかのサービスによって消費される既存のインターフェースを変更する必要がなくなり、サービスは必要に応じて追加のパラメーターを渡すことができました。golang playgroundのサンプルコード:https : //play.golang.org/p/G668FA97Nu


3

Go言語はメソッドのオーバーロードをサポートしていませんが、オプションのパラメーターと同じように可変引数を使用できます。また、パラメーターとして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
golang


0

別の可能性は、有効かどうかを示すフィールドを持つ構造体を使用することです。NullStringなどのSQLのnull型は便利です。独自のタイプを定義する必要がないのは良いことですが、カスタムデータタイプが必要な場合は、常に同じパターンに従うことができます。オプション性は関数定義から明らかで、余分なコードや労力は最小限で済むと思います。

例として:

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.