Why Go language paradigm?

Go: Why bring paradigm

Introduction

[It is published in the Gophercon 2019 speech version. Video links are available. ]

This article is about the significance of adding generics Go , and why I think we should do it. I will also add generic design may introduce changes to Go.

Go on November 10, 2009 release. Less than 24 hours later, we saw about generics in the first comment . (The review also mentioned that we added in the form of panic and recover the language of the case in early 2010.)

Go survey in three years, the lack of generics has been listed as one of the three issues of the language needs to be fixed.

Why are generics?

But with the addition of generics What does it mean, why do we want it?

With  Jazayeri et al words generic programming functions and data structures can be represented in the form of general types:.

What does that mean?

To take a simple example, let's assume we want to reverse the elements slice. This is not a program needs to do a lot of things, but not so unusual.

Let's say it is a int slice.

func ReverseInts(s []int) {
    first := 0
    last := len(s)
    for first < last {
        s[first], s[last] = s[last], s[first]
        first++
        last--
    }
}

Very simple, but even a simple function, you also want to write test cases. In fact, when I do this, I found a mistake. I'm sure many readers have found it.

func ReverseInts(s []int) {
    first := 0
    last := len(s) - 1
    for first < last {
        s[first], s[last] = s[last], s[first]
        first++
        last--
    }
}

We need to minus 1 in the final set variables.

Now let's reverse a string.

func ReverseStrings(s []string) {
    first := 0
    last := len(s) - 1
    for first < last {
        s[first], s[last] = s[last], s[first]
        first++
        last--
    }
}

If the comparison ReverseInts and ReverseStrings, you will see two functions are identical, except for the type parameter. I think there is no reader be surprised.

Go surprised some people, there is no way to write a simple function Reverse apply to any type of slice.

Most other languages ​​allow you to write this function.

In like Python or JavaScript dynamically typed language, you can simply write a function, without specifying the element type. This does not work in Go because Go is statically typed, and ask you to type the exact type of slice and slice elements of a note.

Most other statically typed languages, such as C ++ or Java or Rust or Swift, support for generics to solve such problems.

Today's general-purpose programming Go

So how do people write such code in Go?

In Go, you can use the interface type is suitable for the preparation of a single function of different types of slices, slice and define the method on the type to be passed. This is how sort.Sort function of the standard library.

In other words, Go interface in the form of a common type of programming. They allow us to capture the common aspects of different types, and expressing them as a method. Then we can write these interface types of functions that would apply to any type of implementation of these methods.

However, this approach does not meet our requirements. Use interface, you must write your own method. There are several ways to reverse type definitions named slice is very embarrassing. The method you write for each slice types are identical, so in a sense, we just moved and compressed duplicate code, we do not eliminate it. Although the interface is generic form, but they did not provide all generic we want to us.

Another method uses a generic interface that can address the need to write your own method is to make language definitions for some forms. This is not supported by the language of things, but, for example, the language can be defined for each slice type has an element of the Index method returns. But in order to use this method in practice, it must return an empty interface type, then we lose all the benefits of static typing. More subtly, there is no way to define a generic function that uses two different slices of the same element type, or to use a type of element mapping and return the same type of slice elements. Go is a statically typed language, because it makes it easier to write large programs; we do not want to lose the benefits of static typing, in order to obtain the benefits of generics.

Another method is to use Reverse reflection package to write a generic function, but this is very awkward to write and run slow, very few people do it. The method also requires explicit type assertion and no static type checking.

Alternatively, you can write a code generator that takes a type and Reverse generate a slice for that type of function. There are several code generator is to do so. But it added to each packet needs another step Reverse, it makes build complicated because you must compile all different copies, and the repair of the main sources of error need to regenerate all instances, some examples might be completely different project.

All of these methods are very awkward, I think most of the sections must be reversed in Go are just written a function for a specific type of chips they need. Then they need to write test cases as a function, to ensure that they did not make a simple mistake like I originally made. And they need to run these tests on a regular basis.

But we do so, which means that in addition to the element type, look for the exact same function, there are still a lot of extra work. Not that it could not be completed. Obviously you can do, Go programmers are doing it. But there should be a better way.

For like Go statically typed language, a better approach is generic. I had to write a generic programming can be represented in the form of generic functions and data structures, and type into account. This is what we want.

Go can bring to what generics

We want first and most important thing to get from Go generics are able to write a function, Reverse without concern for a slice of the element type. We want to break down the kind of element type. Then we can write a function, write test once, put them in a go-gettable package, and call them at any time.

Even better, since this is an open-source world, other people can write Reverse time, we can use their realization.

At this point, I should say "generic" could mean many different things. In this article, I said, "generic" is what I just described. In particular, I do not mean C ++ template language that supports content than I am here to write much more.

I Reverse described in detail, but we can write a lot of other features, such as:

  • Find a slice of min / max element
  • Slices obtained average value / standard deviation
  • Computing joint / cross maps
  • Find the shortest path in the node / edge in
  • The transfer function is applied to slice / map, returns the new slice / map

These examples are provided in most other languages. In fact, I write this list by browsing the C ++ Standard Template Library.

There are some specific examples of Go, it strongly supports concurrency.

  • Read from the channel having a timeout
  • The two channels are combined into a channel
  • Parallel calls function list, returns a result
  • Call the function list, use Context, returns the results of the first function to complete, canceled, and clean up the extra goroutines

I've seen all of these functions written many times with different types. Go write in them is not difficult. But it can be reused for any value type of efficient and easy to implement will be good debugging.

It should be noted that these are just a few examples. There are many common functions can be used to write generic easier and safely.

In addition, as I wrote before, it is not just functional. It is also the data structure.

Go two generic data structures built in the common language: slicing and maps. And slice maps can hold any type of data value, using the value of the static type checking storage and retrieval. Value is stored as its own, rather than the interface type. That is, when I have a [] int, int slices stored directly, instead of int to an interface type.

Slice and maps are most useful common data structures, but they are not unique. Here are some other examples.

  • Sets
  • Self-balancing tree, insert and orderly row ordered traversal
  • Multimaps, having a plurality of keys instances
  • Concurrent hash map, and find support concurrent inserts, no single lock

If we can write a generic type, we can define a new data structure like this, they have a slice and map the same type checking advantage: the compiler can statically type check their holdings types of values, and values ​​can be stored for their own, rather than as an interface type.

It should also be mentioned algorithm may be adopted and apply them to the generic data structure.

These examples should like Reverse: General functions and data structures write once, in a package, and reused when needed. They should work like slices and maps the same, because they should not be stored Null interface type, it should be stored in a particular type, and should check these types at compile time.

Go This is something that can be obtained from generics. Generics can provide a powerful building block for us, so that we can more easily share code and build the program.

I hope I have explained why worth studying.

Benefits and costs

But paradigm does not come from Big Rock Candy Mountain , where sunlight a day on the lemonade springs . Each language has its costs change. There is no doubt, added generics will be more complex language to Go. As with any language change, we need to talk to maximize the benefits and minimize costs.

In Go, our goal is to reduce complexity by independent, orthogonal language function can be freely combined. Let's reduce complexity by simplifying the various functions, and by allowing the combination to maximize the benefits of the free function. We hope that generics do the same thing.

To make this more concrete, I will list some guidelines that we should follow.

* Minimize new concept

We should add as little as possible for the new concept of language. This means a minimum of new syntax and add a minimum of new keywords and other names.

* Complexity of writers who fell common code, rather than the user's body

General Packet programmer programming complexity should be reduced as much as possible. We do not want users do not have to worry about generic package. This means that you should be calling a generic function in a natural way, any errors while using a common package should be easy to understand and fix to the way the report. The call also called for a common code should be easy.

* Writers and users can work independently

Similarly, we should be very easy to authors and users of common code concerns apart, so that they can independently develop code. They should not worry about what the other is doing, not just the normal function of different packages writers and callers have to worry. It sounds obvious, but for all other programming languages ​​are not so generic.

* Build time is short, short execution time

Of course, as much as possible, we want to keep Go today to give us a short construction time and fast execution time. Generic tend to trade off quickly build and rapid execution. We both want as much as possible.

* Go to maintain clarity and simplicity

The most important thing is, Go today is a simple language. Go programs are usually clear and understandable. A major part of our long process of exploring this space are trying to understand how to add generics, while maintaining clarity and simplicity of. We need to find mechanisms for existing languages, rather than turn it into something entirely different.

These guidelines shall apply to any generic implementation of Go. This is what I want to leave you with today is the most important information: Generics can bring significant benefits for the language, but if you still feel like a Go Go then they are worth doing.

The draft design

Fortunately, I think it can be done. To complete this article, I will discuss why we want to generics, as well as what is required of them is that we believe that a brief discussion of how to add them to the language of design.

In this year's Gophercon Robert Griesemer and I released a draft design , add generics Go. For more information, please refer to the draft. I will discuss here some key points.

This is a generic reverse this design function.

func Reverse (type Element) (s []Element) {
    first := 0
    last := len(s) - 1
    for first < last {
        s[first], s[last] = s[last], s[first]
        first++
        last--
    }
}

You will notice that the body exactly the same function. Only the signature change occurred.

It has been considered the element type slice. It is now named Element and become what we call the type parameter. It is not to be part of a slice parameter type, but a single type of additional parameters.

To use the type of function is called, in general, you pass a type parameter, which is similar to any other parameters, but it is a type.

func ReverseAndPrint(s []int) {
    Reverse(int)(s)
    fmt.Println(s)
}

This is behind the Reverse (int) in this case is what you see.

Fortunately, in most cases, including this, the compiler may be inferred from the parameters of the conventional type of the parameter type, parameter types, and do not need to be mentioned.

Calling a generic function is just like calling any other function.

func ReverseAndPrint(s []int) {
    Reverse(s)
    fmt.Println(s)
}

In other words, although GM Reverse function slightly more complex ReverseInts and ReverseStrings, landed on this complex function, instead of writing and calling.

contract

Since Go is a statically typed language, we have to discuss the type of the parameter type. This primitive type to tell what type of parameters, and the generic function allows the compiler what can be done on the value of the type parameter when calling a generic function.

The Reverse function can use any type of slice. Its sole function Element Type value is assigned, it applies to any type of Go. For this generic function, which is a very common situation, we do not need to say what particular type parameters.

Let's take a quick look different functions.

func IndexByte (type T Sequence) (s T, b byte) int {
    for i := 0; i < len(s); i++ {
        if s[i] == b {
            return i
        }
    }
    return -1
}

Currently, the standard library package and strings of bytes package has a IndexByte function. This function returns the index b s sequence, wherein sa string or a [] byte. We can use this function to replace a single generic packet byte strings and two functions. In practice, we may not do, but this is a simple example of a useful.

Here, we need to know the effect type parameter T is similar to a string or a [] byte. We can call it len, we can index it, we can be the result of the operation of the index compared with the byte value.

To compile, type parameter T itself needs a type. It is a primitive type, but because we sometimes need to describe the type of multiple related types, and because it describes the relationship between the caller achieve its generic function, so we actually call a T contract. Contract here named Sequence. It appears after the type parameter list.

This is an example of Sequence contract for this purpose is defined way.

contract Sequence(T) {
    T string, []byte
}

This is easy, because this is a simple example: type parameter T may be a string or [] byte. This contract may be a new keyword or special identifier identified within the scope of the package; refer to the draft design for more information.

We remember any show ever on Gophercon 2018 design people will discover this way of signing the contract is much simpler. We got a lot of feedback about the contract early overly complex designs, we have attempted to be taken into account. Write, read and understand the new contracts would be much simpler.

They allow you to type the basis of the type of parameters, and / or the type parameter list of the method specified. They can also let you describe the relationship between different types of parameters.

Signed a contract with the method

This is another simple example, a method which returns a String [] a string representation of all elements string s.

func ToStrings (type E Stringer) (s []E) []string {
    r := make([]string, len(s))
    for i, v := range s {
        r[i] = v.String()
    }
    return r
}

It is very simple: to traverse sections, String invoke methods on each element, and then returns the resulting string slice.

This function requires the element type String implemented method. String contract to ensure this.

contract Stringer(T) {
    T String() string
}

T must implement the contract but said the String method.

You may notice this contract looks like fmt.Stringer interface, so is worth noting that not a function parameter ToStrings branch fmt.Stringer. It is the type of some elements of the element type fragment fmt.Stringer achieved. Slice represents the element type of memory chips and fmt.Stringer usually different, Go does not support direct conversion between them. So even fmt.Stringer exist, it is worth writing.

There are many types of contracts

The following is an example of a contract with a plurality of types of parameters.

type Graph (type Node, Edge G) struct { ... }

contract G(Node, Edge) {
    Node Edges() []Edge
    Edge Nodes() (from Node, to Node)
}

func New (type Node, Edge G) (nodes []Node) *Graph(Node, Edge) {
    ...
}

func (g *Graph(Node, Edge)) ShortestPath(from, to Node) []Edge {
    ...
}

Here we describe a Graph built by Node and Edge. We do not need a specific data structure of Graph. On the contrary, we must have a say Node type Edges method that returns a list of Node to connect to the Edge. And Edge Nodes return type must have a method of two Nodes, the Edge connected.

I've skipped the realization, but it shows a New Graph function returns a signature, and a signature Graph ShortestPath methods.

The important element here is not just one type of contract. It can describe the relationship between two or more types.

Ordered type

A surprisingly common complaint about the Go is that it does not function Min. Or, for that matter, a Max function. This is because a useful Min function should be applicable to any type of order, which means it must be universal.

Although Min write their own very simple, but any useful generic implementation should allow us to add it to the standard library. This is our design.

func Min (type T Ordered) (a, b T) T {
    if a < b {
        return a
    }
    return b
}

T said the contract has a type Ordered ordered type, which means it supports as less than, greater than, and other operations.

contract Ordered(T) {
    T int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64, uintptr,
        float32, float64,
        string
}

The contract is only Ordered all commands defined by the language type of list. This contract listed accept any type, base type or a type in which any named type. Basically, you can use any type less operator.

It has proved to support the enumerated type operator is less than a ratio invention is applicable to all new notation operators much easier. After all, in Go, only the built-in type support operator.

This method can be used with any operator, or, more generally, any contract for the preparation of generic functions are intended for use with the built-in type. It allows authors generic functions clearly specify the type of function to be set for use therewith. It allows the caller to common functions clearly see whether the function is suitable for the type of use.

In fact, the contract may go into the standard library. So Min function (also possible in a standard library) look like this. Here we just mention Ordered contract package defined in the contract.

func Min (type T contracts.Ordered) (a, b T) T {
    if a < b {
        return a
    }
    return b
}

Common data structure

Finally, let's look at a simple common data structure, a binary tree. In this example, the tree having a comparison function, and therefore does not require the type of the elements.

type Tree (type E) struct {
    root    *node(E)
    compare func(E, E) int
}

type node (type E) struct {
    val         E
    left, right *node(E)
}

Here's how to create a new binary tree. New functions are passed to the comparison function.

func New (type E) (cmp func(E, E) int) *Tree(E) {
    return &Tree(E){compare: cmp}
}

The method returns a pointer to non-export slot pointer holds v or should go to the tree pointing to a position.

func (t *Tree(E)) find(v E) **node(E) {
    pn := &t.root
    for *pn != nil {
        switch cmp := t.compare(v, (*pn).val); {
        case cmp < 0:
            pn = &(*pn).left
        case cmp > 0:
            pn = &(*pn).right
        default:
            return pn
        }
    }
    return pn
}

The details are not important here, especially since I have not tested this code. I just want to show the way to write a simple generic data structure.

It is used to test whether the tree contains the code values.

func (t *Tree(E)) Contains(v E) bool {
    return *t.find(e) != nil
}

This new value is inserted into the code.

func (t *Tree(E)) Insert(v E) bool {
    pn := t.find(v)
    if *pn != nil {
        return false
    }
    *pn = &node(E){val: v}
    return true
}

Note E type parameter of type node. This is the way to write generic data structure. As you can see, it looks like an ordinary Go write the code, except here and there scattered some types of parameters.

Use the tree is very simple.

var intTree = tree.New(func(a, b int) int { return a - b })

func InsertAndCheck(v int) {
    intTree.Insert(v)
    if !intTree.Contains(v) {
        log.Fatalf("%d not found after insertion", v)
    }
}

this is necessary. Development of generic data structures to be more difficult, because you often need to support the type of explicit type parameter write, but to use as a little different from the ordinary use of non-generic data structures.

The next step

We are carrying out the actual implementation, so that we can try this design. We can try to design very important in practice to ensure that we can write the type of program we want to write. It is not as fast as we want, but we will send more details when these implementations are available.

Robert Griesemer prepared a preliminary CL , modify the go / types package. This allows testing whether the use of generics and contract type of code can be checked. It is not yet complete, but it is mainly applied to a single package, we will continue to work hard.

We want people to realize this and future do is try to write and use common code and see what happens. We want to ensure that people can write code they need, and they can use it as intended. Of course, not everything will work when we explore this space, we may have to change everything. And, to be clear, our feedback than semantic details of grammar more interested.

I want to thank all those who comment on the early design, and all discussed in Go paradigm of people. We have read all the comments, we are very grateful for the efforts of people to pay. Without that work, we would not have today's design and development work.

Our goal is to achieve a design that allows me to write generic code that I have discussed today, without making the language too complex or not to use Go. We hope that this design is a step towards this goal, we hope to continue to adjust it in the course of our learning from our experience and your experience, what works, what does not. If we do achieve that goal, then we can think that future versions of the Go provides some suggestions.

Author: Ian Lance Taylor

Original: https://blog.golang.org/why-generics

Translation: https://github.com/llgoer/go-generics

Guess you like

Origin www.oschina.net/news/108697/why-golang-generic