Golang的参数化多态性?

huangapple go评论73阅读模式
英文:

Golang Parametric polymorphism?

问题

我写了一个函数来计算浮点数数组的标准差,但是我遇到了一个问题,如果我有一个整数数组,我该如何使用它呢?

func StdDev(a []float64) float64 {
    var Prom float64
    sum := 0.0
    Total := 0.0
    n := len(a)
    N := float64(n)

    for i := 0; i < n; i++ {
        sum += a[i]
    }
    Prom = sum / N
    for i := 0; i < n; i++ {
        Total += (a[i] - Prom) * (a[i] - Prom)
    }
    Total = Total / N
    Total = math.Sqrt(Total)
    return Total
}

我不想为每种数据类型编写一个函数。

英文:

I wrote a function in order to get the standard deviation from a
array of floats, but I'm have a problem, how can I use it
if I have a array of ints?<br>
I dont want to have a function for every data type...

func StdDev(a []float64) float64 {
	var Prom float64
	sum := 0.0
	Total := 0.0
	n := len(a)
	N := float64(n)

	for i := 0; i &lt; n; i++ {
		sum += a[i]
	}
	Prom = sum / N
	for i := 0; i &lt; n; i++ {
		Total += (a[i] - Prom) * (a[i] - Prom)
	}
	Total = Total / N
	Total = math.Sqrt(Total)
	return Total
}

答案1

得分: 4

Go语言没有泛型,因此无法同时处理[]int[]float64。你需要使用一个简单的for循环和类型转换,将[]int中的值复制到[]float64中。然后你就可以使用你的函数了。示例代码如下:

a := []int{1,2,3,4}
b := make([]float64, len(a))

for i := range a {
    b[i] = float64(a[i])
}

StdDev(b)

你还可以基于反射值编写一个函数,然后使用reflect.MakeFunc来使用。这种方法会更慢,更难实现,并且需要编写更多的代码,所以效益是可疑的。

代码风格方面,虽然这是离题的,但我还是注意到你的代码可能会更好看一些,如果你在循环中使用range子句。此外,函数体中的变量应该使用小写字母。还有一些其他的语法技巧,比如命名返回值。利用Go语言的这些特性,你的代码可能会更加优雅:

func Avg(a []float64) (sum float64) {
    for i := range a {
        sum += a[i]
    }
    return sum / float64(len(a))
}

func StdDev(a []float64) (total float64) {
    prom := Avg(a)

    for i := range a {
        total += (a[i] - prom) * (a[i] - prom)
    }

    total = total / float64(len(a))

    return math.Sqrt(total)
}
英文:

Go has no generics, so you are not able to write a solution that covers []int and []float64 at the same time. You have to copy the values from your []int to a []float64 using a simple for-loop and a type conversion from int to float. Then you can use your function. Example (play):

a := []int{1,2,3,4}
b := make([]float64, len(a))

for i := range a {
	b[i] = float64(a[i])
}

StdDev(b)

What you can also do is to write a function based on reflection values and then use reflect.MakeFunc. This would be slower, harder to make and more code to write so the benefit is questionable.

Code style

Although this is off-topic I can't help but noticing that your code may look nicer if you would use range clauses for your loops. Also, variables in a function body are written in lower caps. There are some other syntactic tricks such as named return values. Utilizing these features of Go, your code may look nicer:

func Avg(a []float64) (sum float64) {
	for i := range a {
		sum += a[i]
	}
	return sum / float64(len(a))
}

func StdDev(a []float64) (total float64) {
	prom := Avg(a)
	
	for i := range a {
		total += (a[i] - prom) * (a[i] - prom)
	}
	
	total = total / float64(len(a))

	return math.Sqrt(total)
}

答案2

得分: 2

你可以使用接口,就像sor包一样:

package main

import "math"

type Adder interface {
    Add(a float64) float64
}

type floatAdder float64

func (f floatAdder) Add(a float64) float64 {
    return float64(f) + a
}

type intAdder int

func (i intAdder) Add(a float64) float64 {
    return float64(i) + a
}

func StdDev(a []Adder) float64 {
    var Prom float64
    sum := 0.0
    Total := 0.0
    n := len(a)
    N := float64(n)

    for i := 0; i < n; i++ {
        sum = a[i].Add(sum)
    }
    Prom = sum / N
    for i := 0; i < n; i++ {
        Total += a[i].Add(-Prom) * a[i].Add(-Prom)
    }
    Total = Total / N
    Total = math.Sqrt(Total)
    return Total
}

func main() {
    floats := []Adder{floatAdder(1.0), floatAdder(2.0), floatAdder(3.0)}
    println(StdDev(floats))

    ints := []Adder{intAdder(1), intAdder(2), intAdder(3)}
    println(StdDev(ints))
}

这段代码定义了一个接口Adder,它有一个Add方法。然后定义了两个类型floatAdderintAdder,它们分别实现了Add方法。StdDev函数接受一个Adder类型的切片,并计算标准差。在main函数中,分别使用floatAdderintAdder类型的切片调用StdDev函数,并打印结果。

英文:

You can use interfaces, just like the sort package does:

http://play.golang.org/p/4N_UpFScoU

package main

import &quot;math&quot;

type Adder interface {
	Add(a float64) float64
}

type floatAdder float64

func (f floatAdder) Add(a float64) float64 {
	return float64(f) + a
}

type intAdder int

func (i intAdder) Add(a float64) float64 {
	return float64(i) + a
}

func StdDev(a []Adder) float64 {
	var Prom float64
	sum := 0.0
	Total := 0.0
	n := len(a)
	N := float64(n)

	for i := 0; i &lt; n; i++ {
		sum = a[i].Add(sum)
	}
	Prom = sum / N
	for i := 0; i &lt; n; i++ {
		Total += a[i].Add(-Prom) * a[i].Add(-Prom)
	}
	Total = Total / N
	Total = math.Sqrt(Total)
	return Total
}

func main() {
	floats := []Adder{floatAdder(1.0), floatAdder(2.0), floatAdder(3.0)}
	println(StdDev(floats))

	ints := []Adder{intAdder(1), intAdder(2), intAdder(3)}
	println(StdDev(ints))
}

答案3

得分: 0

截至2021年8月20日,Go语言有一个类型参数提案,旨在为Go语言添加参数多态性或泛型。

> 我们目前预计这个变化将在2022年初的Go 1.18版本中可用。

根据该提案,接口的功能将扩展以包括类型集,可以合并不同的类型。

该提案提供了一个示例,用于将数字元素的列表加倍,这应该有助于澄清参数多态性如何解决类似StdDev的问题:

// Double返回一个包含s中所有元素加倍的新切片。
func Double[E constraints.Number](s []E) []E {
    r := make([]E, len(s))
    for i, v := range s {
        r[i] = v + v
    }
    return r
}

关于约束包API的讨论可以在这里找到,源代码可以在这里查看。

似乎没有constraints.Numberconstraints.Numeric,所以你需要创建一个,但这很简单:

type RealNumber interface {
    Integer | Float
}

然后你的函数的语法应该很简单:

func StdDev[E RealNumber](data []E) float64 {
    // 计算平均值
    sum := 0.0
    for _, datum := range data {
        sum += datum
    }
    mean := sum / len(a)

    // 计算方差
    sum = 0.0
    for _, datum := range data {
        deviation := datum - mean
        sum += deviation * deviation
    }
    variance := sum / len(a)

    return math.Sqrt(variation)
}
英文:

As of August 20, 2021, GoLang has a Type Parameters Proposal to add parametric polymorphism, or generics, to GoLang.

> We currently expect that this change will be available in the Go 1.18 release in early 2022

Under that proposal, the capabilities of interfaces will be expanded to include Type Sets, which can Union different types.

The proposal gives an example of doubling a list of numeric elements, which should help clarify how parametric polymorphism can solve problems like the StdDev one:

// Double returns a new slice that contains all the elements of s, doubled.
func Double[E constraints.Number](s []E) []E {
	r := make([]E, len(s))
	for i, v := range s {
		r[i] = v + v
	}
	return r
}

There is a discussion on the constraint package API here, and the source can be viewed here.

There does not appear to be a constraints.Number or constraints.Numeric, so you would need to make one, but that is simple:

type RealNumber interface {
	Integer | Float
}

And then the syntax of your function should be straightforward:

func StdDev[E RealNumber](data []E) float64 {
    // determine the mean
    sum := 0.0
    for _, datum := range data {
        sum += datum
    }
    mean := sum / len(a)

    // calculate the variance
    sum = 0.0
    for _, datum := range data {
        deviation := datum - mean
        sum += deviation * deviation
    }
    variance := sum / len(a)

    return math.Sqrt(variation)
}

huangapple
  • 本文由 发表于 2014年7月23日 00:15:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/24892565.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定