ylliX - Online Advertising Network
Constraints in Go — Bitfield Consulting

Constraints in Go — Bitfield Consulting


Design is the beauty of turning constraints into
advantages.

Aza
Raskin

This is the fourth in a four-part series of tutorials on generics in
Go.

  1. Generics
  2. Type parameters
  3. Generic types
  4. Constraints

In my book Know Go, and in the previous
tutorials in this series, you’ll learn all about generic programming in
Go and the new universe of programs it opens up to us. Ironically, one
of the new features of Go that gives us the most freedom is
constraints. Let’s talk about that, and explain the
paradox.

We saw in the previous tutorial
that when we’re writing generic functions that take any type, the range
of things we can do with values of that type is necessarily
rather limited. For example, we can’t add them together. For that, we’d
need to be able to prove to Go that they’re one of the types that
support the + operator.

Method set constraints

It’s the same with interfaces, as we discussed in the first post in this series. The empty
interface, any, is implemented by every type, and so
knowing that something implements any tells you nothing
distinctive about it.

Limitations of the
any constraint

Similarly, in a generic function parameterised by some type T,
constraining T to any doesn’t give Go any information about
it. So it has no way to guarantee that a given operator, such as
+, will work with values of T.

A Go proverb says:

The bigger the interface, the weaker the abstraction.
https://go-proverbs.github.io/

And the same is true of constraints. The broader the constraint, and
thus the more types it allows, the less we can guarantee about what
operations we can do on them.

There are a few things we can do with any
values, as you already know, because we’ve done them. For example, we
can declare variables of that type, we can assign values to them, we can
return them from functions, and so on.

But we can’t really do a whole lot of computation with them,
because we can’t use operators like + or -. So
in order to be able to do something useful with values of T, such as
adding them, we need more restrictive constraints.

What kinds of constraints could there be on T? Let’s examine
the possibilities.

Basic interfaces

One kind of constraint that we’re already familiar with in Go is an
interface. In fact, all constraints are interfaces of a kind,
but let’s use the term basic interface here to avoid any
confusion. A basic interface, we’ll say, is one that contains only
method elements.

For example, the fmt.Stringer interface we saw in the first tutorial:

first
tutorial
.

There’s no advantage to writing a generic function in this case,
since we can use this interface type directly in an ordinary function.
All the same, a basic interface—one defined by a set of methods—is a
valid constraint for type parameters, and we can use it that way if we
want to.

Exercise: Stringy beans

Flex your generics muscles a little now, by writing a generic
function constrained by fmt.Stringer to solve the stringy
exercise.

Listing
exercises/stringy
)

GOAL: Your job here is to write a generic function
StringifyTo[T] that takes an io.Writer and a
value of some arbitrary type constrained by fmt.Stringer,
and prints the value to the writer.


HINT: This is a bit like the
PrintAnything function we saw before, isn’t it? Actually,
it’s a “print anything stringable” function. We already know what the
constraint is (fmt.Stringer), and the rest is
straightforward.


SOLUTION: Here’s a version that would work, for
example:

Listing
solutions/stringy
)

Strictly speaking, of course, we don’t really need to call the
String method: fmt already knows how to do
that automagically. But if we just passed p directly, we
wouldn’t need the Stringer constraint, and we could use
any… but what would be the fun in that?

Type set constraints

We’ve seen that one way an interface can specify an allowed range of
types is by including a method element, such as
String() string. That would be a basic interface, but now
let’s introduce another kind of interface. Instead of listing methods
that the type must have, it directly specifies a set of types that are
allowed.

Type elements

For example, suppose we wanted to write some generic function
Double that multiplies a number by two, and we want a type
constraint that allows only values of type int. We know
that int has no methods, so we can’t use any basic
interface as a constraint. How can we write it, then?

Well, here’s how:

previous
tutorial
, such as []E, which is a slice of some element
type E.

But we’re not restricted to defined types with names. We can also
construct new types on the fly, using a type literal: that is,
literally writing out the type definition as part of the interface.

A struct type literal

For example, this interface specifies a struct type
literal:

intish
challenge?

Here you’re provided with a function IsPositive, which
determines whether a given value is greater than zero:

Listing
exercises/intish
)

And there’s a set of accompanying tests that instantiate this
function on some derived type MyInt:

Listing
exercises/intish
)

GOAL: Your task here is to define the
Intish interface.


HINT: A method set won’t work here, because the
int type has no methods! On the other hand, the
type literal int won’t work either, because
MyInt is not int, it’s a new type derived from
it.

What kind of constraint could you use instead? I think you know where
this is going, don’t you? If not, have another look at the previous
section on type approximations.


SOLUTION: It’s not complicated, once you know that a
type approximation is required:

Listing
solutions/intish
)

Interface literals

Up to now, we’ve always used type parameters with a named
constraint, such as Integer (or even just
any). And we know that those constraints are defined as
interfaces. So could we use an interface literal as a type
constraint?

Syntax of an interface
literal

An interface literal, as you probably know, consists of the keyword
interface followed by curly braces containing (optionally)
some interface elements.

For example, the simplest interface literal is the empty interface,
interface{}, which is common enough to have its own
predeclared name, any.

We should be able to write this empty interface literal wherever
any is allowed as a type constraint, then:

Know Go. If these tutorials have given you an
appetite for generic programming in Go, I think you’ll really enjoy the
book—check it out!

Exercise: Greater love

Your turn now to see if you can solve the greater
exercise.

You’ve been given the following (incomplete) function:

Listing
exercises/greater
)

This takes two values of some arbitrary type, and compares them by
calling the Greater method on the first value, passing it
the second value.

The tests exercise this function by calling it with two values of a
defined type MyInt, which has the required
Greater method.

Listing
exercises/greater
)

GOAL: To make these tests pass, you’ll need to write
an appropriate type constraint for IsGreater. Can you see
what to do?


HINT: Remember, we got here by talking about
constraints as interface literals, and in particular, interface literals
that refer to the type parameter.

If you try to define some named interface with the method
set containing Greater, for example, that won’t work. We
can’t do it for the same reason that we couldn’t define a named
interface with the method set Equal: we don’t know what
type of argument that method takes.

Just like Equal, Greater takes arguments of
some arbitrary type T, so we need an interface literal that can
refer to T in its definition. Does that help?


SOLUTION: Here’s one way to do it:

Listing
solutions/greater
)

Like most things, it’s delightfully simple once you know. For a type
parameter T, the required interface is:

And that’s how we do that.

Well, I hope you enjoyed this tutorial series, and if so, why not
treat yourself to a copy of Know Go?
There’s much more to explore, so I’d love you to come along with me for
the ride.



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *