2

あいまいなタイトルで申し訳ありません。

私はこの本http://algs4.cs.princeton.edu/home/を読んでいて、学習演習として Go で例を実装するのが良いと思いましたが、この本ではコードを記述する言語として Java を使用しています。 .

最初の章の 1 つで、後で再利用するためのいくつかのコア データ型/コンテナ スタイル クラスの設定について説明していますが、主にこれらのデータ型が Java ジェネリックの使用を楽しんでいるように見えるため、これらを Go 設定に組み込むのに苦労しています。

たとえば、私は次のコードを書きました

package bag

type T interface{}
type Bag []T

func (a *Bag) Add(t T) {
    *a = append(*a, t)
}

func (a *Bag) IsEmpty() bool {
    return len(*a) == 0
}

func (a *Bag) Size() int {
    return len(*a)
}

Bagこれは、アイテムを に追加し、そのサイズとすべてを確認できるという意味で、原則として機能します。ただし、これは次のコードが有効であることも意味します

a := make(bag.Bag,0,0)
a.Add(1)
a.Add("Hello world!")
a.Add(5.6)
a.Add(time.Now())

タイプを強制して、次のような契約に準拠する方法があるかどうか疑問に思っていました

Bag<T> bagOfMyType = new Bag<T>()

例えば

Bag<Integer> bagOfInts = new Bag<Integer>()

Goにはジェネリックがなく、実際にはGo Wayではないことは知っていますが、コンパイル時に何かを「強制」できるかどうか疑問に思っていました(おそらくそうではありません)

長文すみません

編集:わかりましたので、これをもう少し詳しく調べてきました。ジェネリック側のことはほとんどあきらめました (これは Go のロードマップにないことを理解しています)。インターフェイスを持つ Haskell 型クラス。

type T interface{}
type Bag interface {
    Add(t T)
    IsEmpty() bool
    Size() int
}

type IntSlice []int

func (i *IntSlice) Add(t T) {
    *i = append(*i, t.(int)) // will throw runtime exception if user attempts to add anything other than int
}

func (i *IntSlice) IsEmpty() bool {
    return len(*i) == 0
}

func (i *IntSlice) Size() int {
    return len(*i)
}

これに関する問題は、型の強制が実行時にのみ強制されることです。

これを改善する方法はありますか?

4

2 に答える 2

5

私はGo自体が初めてなので、誰かがより良い答えを持っているかどうか知りたいのですが、私はそれをどのように見ていますか:

Add()が で呼び出されたときIntSliceに、そのパラメータが であるというコンパイル時の強制が必要ですint。その方法は次のとおりです。

func (i *IntSlice) Add(t int) {
    *i = append(*i, t)
}

ジェネリックスがないため、Add()メソッドは のすべてのタイプで異なるBagため、Bagインターフェースが必要であると仮定すると、次のようになります。

type Bag interface {
    IsEmpty() bool
    Size() int
}

それは私には理にかなっていますBag。何かが であることを知っているだけでは、それBagを呼び出す方法を知るには十分ではありませんAdd()Bagあなたが扱っているタイプを知っている必要があります。

のように、型に固有のインターフェイスを作成できますが、IntBag実際にそのインターフェイスを満たす型は 1 つだけなので、インターフェイスを削除して の名前をIntSliceに変更することもできますIntBag

基本的に、それはジェネリックのようなものを完全に放棄し、あなたが望むことをするいくつかのメソッドで型を作成することを意味します:

type IntBag []int

func (b *IntBag) Add(i int) {
    *b = append(*b, i)
}

func (b IntBag) IsEmpty() bool {
    return len(b) == 0
}

func (b IntBag) Size() int {
    return len(b)
}

更新:ジェネリックが本当に便利な場合があります。与えられた問題に最適なものをケースバイケースで選択しなければならないように私には思えます。空のインターフェイスとリフレクションを使用すると、ジェネリックのような動作を得ることができますが、見苦しくなる傾向があり、コンパイル時の型チェックを放棄することになります。または、ジェネリックをあきらめて、コードの重複があります。または、まったく別の方法でそれを行うだけです。

数週間前に、クラス階層が必要なように見える問題を処理するために Go を使用する方法について質問しました。答えは基本的に、一般的な解決策はないというものでした。それはすべてケースバイケースです。同じことがジェネリックにも当てはまると思います。Go にはジェネリックはなく、ジェネリックベースのソリューションを Go に変換するための一般的なソリューションもありません。

別の言語でジェネリクスを使用する可能性がありますが、インターフェイスは Go で完全に適切な (または真に優れている) 場合が多くあります。ここでの例は、インターフェイスが実際には適切な代替品ではない例です。参照: Go Vs. ジェネリック

于 2012-08-10T20:52:48.577 に答える
1

私はGoにかなり精通しています。ジェネリックは熱く議論されているトピックであり、現在、Java ジェネリックや C++ テンプレートに類似するものはありません。現時点での慣習は、空のインターフェイスを使用して「ジェネリック」型を実装し、その型の要素のみが使用されるようにする特定の型の実装でラップすることです。container/listGo 標準ライブラリの例を次に示します。

http://play.golang.org/p/9w9H1EPHKR

package main

import (
    "container/list"
    "fmt"
)

type IntList struct {
    innerList *list.List
}

func NewIntList() *IntList {
    return &IntList{list.New()}
}

func (l *IntList) Add(i int) {
    // this is the only way to add an element to the list,
    // and the Add() method only takes ints, so only ints
    // can be added
    l.innerList.PushBack(i)
}

func (l *IntList) Last() int {
    lastElem := l.innerList.Back()

    // We can safely type-assert to an int, because Add()
    // guarantees that we can't put a non-int into our list
    return lastElem.Value.(int)
}

func main() {
    l := NewIntList()
    l.Add(5)
    l.Add(4)
    l.Add(3)
    l.Add(2)
    l.Add(1)
    fmt.Println("Expecting 1; got:", l.Last())
}
于 2014-10-23T20:21:51.057 に答える