user3613179
user3613179

Reputation: 59

Golang Parametric polymorphism?

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

Upvotes: 5

Views: 3424

Answers (3)

MirroredFate
MirroredFate

Reputation: 12826

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

Upvotes: 0

chendesheng
chendesheng

Reputation: 2129

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

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

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

Upvotes: 2

nemo
nemo

Reputation: 57639

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

Upvotes: 4

Related Questions