This does make interfaces much more useful, but doesn't provide
everything generics could. Let's imagine we do have an Ord interface.
Max's signature will look something like
func Max(a, b Ord) Ord
I still have to use a type assertion to get a concrete type back,
which means I don't get type safety at compile time. It also doesn't
provide the performance benefits that one might hope to see from
generics.
- Evan
Can you fill this out a bit? What methods would the Ord interface have, and how could we use them to compare arbitrary numerical types? Only thing I can thing of is a Float64() method, but that is a very inefficient way to do a comparison.
I had a very similar idea for an Enumerable package. An example using
generics can be found for C++ in Qt. Their QList<T> object provides
some niceties. However, to implement this in Go, I was thinking that
there could be convenience functions like:
func (Enumerable) HasAny(func() bool) bool
func (Enumerable) All(func() bool) bool
func (Enumerable) None(func() bool) bool
Where Enumerable could be just a simple interface with an Each
function. For any slice or array type, if you implement an Each
function, then your type will inherent all the Enumerable
functionality (making the code look like Ruby-ish shorthand).
type Ord interface {
LessThan(Ord) bool
}
type Foo int
func (f Foo) LessThan(o Ord) bool {
if o, ok := o.(Foo); ok {
return f < o
}
return true // yuck, but what else to do, panic?
}
func Max(a, b Ord) Ord {
if a.LessThan(b) {
return b
}
return a
}
func main() {
var Foo a = 10, b = 11
c := Max(a, b).(Foo)
fmt.Println(c)
}
Notice how type casts are sprinkled everywhere. Every time you use
the Max function, you need to typecast its result. Every
implementation of LessThan needs to typecast internally in order to
perform the comparison. It's nasty. The primary idea of generics (in
my view) is to allow static typechecking of code like this. Of
course, once you're able to write generic functions, you can also do a
lot more, like possibly use generic data types.
David
--
David Roundy
> type Ord interface {
> LessThan(Ord) bool
> }
This says that everything that can be ordered can be compared to
anything else which can be ordered. This is not very useful.
What the Ord type class in Haskell says is that "an ordered type is a
type whose values can be compared among themselves".
A simple Ord type class in Haskell could be defined like this:
class Ord a where
(<) :: a -> a -> Bool
And a simple max function in Haskell could be written like this:
max :: Ord a => a -> a -> a
max x y
| x >= y = x
| otherwise = y
This is a generic function, so that if you use it on two ints, the
type checker knows that the result will be int and so on..
If we would try to translate this into some kind of imaginary generic
Go it might look something like this:
type Ord interface<Self> {
LessThan(Self) bool
}
generic Ord<T>
func Max(a,b T) T {
if b.LessThan(a) { return a }
return b
}
type Ord interface {
LessThan(interface{}) (bool,error)
}
https://github.com/petar/GoLLRB
Red-Black tree that can handle any kind of value.
--
André Moraes
http://andredevchannel.blogspot.com/
func (f Foo) LessThan(o Ord) bool {
if o, ok := o.(Foo); ok {
return f < o
}
return true // yuck, but what else to do, panic?
}
There are some cases where you can compare two different types (like ints and floats).
I'm not sure how you can tell *in compile time* that two types are comparable.
There are some cases where you can compare two different types (like ints and floats).
I'm not sure how you can tell *in compile time* that two types are comparable.Not in go, you can't...
I thought package-level generics (generic packages) was a reasonable
idea, but it turns out to have some pretty severe flaws in practice.
The biggest one is that it requires you to put any data type that you
want to deal with generically in a separate package. Very often,
you'd like to do something like:
type Foo struct { ... }
func (f *Foo) MyMethod() []Foo {
[code that uses generic code on lists of Foo, but can't, since it
can't import a package that uses Foo]
}
Of course, you can always work around these things using type synonyms
and so forth, but it doesn't improve the readability of code.
There are also some technical difficulties in using gotgo (which
probably is sufficiently bitrotted that it will no longer compile),
but the biggest issue is that package-level generics just don't
address that many of the situations where you'd really like to use
generics.
David
The generic dilemma is this: do you want slow programmers, slow compilers and bloated binaries, or slow execution times?
I think it's important to realize that there are different ways to
implement generics. In order to be able to use generics to make the
standard library much faster at runtime, you need to use an
implementation along the lines of C++ templates, in which each function
that uses a generic type is in effect rewritten to use that type and
then recompiled. This approach means that compilation slows down.
"Rewriting the generic code is not a bottleneck, but compiling the generic code
multiple times is. Also the question arises of when the code is
compiled; if done
at compile time, as most C++ compilers do, then if two different
packages want to
sort ints, the compiler has to compile the sort<int> code twice. "
Why should be sort<int> compiled more than once?
When compiling the first time you cache it, the object is available somewhere (as a .a static library or the go compiler produced equivalent) to be linked by the other packages that need sort<int>.In fact it should be like a package appendix. For instance, lets say packages sort, sortuser and the main.1) sort is compiled, the non generic part produces a sort.a2) sortuser expands sort<int> so it produces sortuser.a and sort_int.a
3) main uses sort<string> and sort<int> and so it produces main.a, sort_string.a BUT sort_int.a is already available.At link phase the program is composed of sort.a, sort_int.a (once), sort_string.a, sortuser.a and main.aWhy would it had to be different?
For example, one could allow a special type '_' in interface declarations to mean "the same type as the method's receiver" and then declare interfaces like this:
type Lesser interface {
Less(_) bool
}
Greetings,
Maybe we don't need generics. Maybe the solution is enhancing interfaces to
cover basic types.
This idea came to me while going over http://learnyouahaskell.com. There if you
want to have a generic "max" function, it takes "Ord" typeclass (interface). If
we'll have Go numbers also implement "Ord" interface then a generic "max" will
be easy to write.
What are your thoughts on this?
Thanks,
--
Miki
> Thanks for the explanation. To me that approach sounds very much likeThanks for the feedback.
> Java's type erasure approach. I think that approach has a serious
> problem: when I pass List~int to a function that takes interface{}, I've
> lose the type information. There is nothing preventing me from doing a
> type assertion to List~float64; I do get the runtime checks when I try
> to pull the values out, but I don't know the type of the container.
>
> Ian
I'm afraid I don't understand how this problem is specific to the '~T' approach: when you pass *anything* to func(interface{}) you lose the type information, except that you can recover it through reflection/inference plus type assertions. What are you hoping would be handled better?
One thing that's nice about a []int is that you can type assert to it. If you have a List<int> and pass it to an interface{}, with type erasure it is simply a List at runtime and you cannot, for instance, distinguish easily between what used to be a List<int> and List<string>.
> Isn't that contrary to the purpose of type erasure?I never intended type erasure: I intend type preservation.
> And they can't have the same instructions if it's type List~T []T.Do you mean "And they can't have the same instructions if it's type List~T versus List~([]T)"? The latter is illegal, since T must be an interface type. The closest you could get to List~([]T) would be List~(Slice~T) where Slice is a generic interface.
josvazg <jos...@gmail.com> writes:
> So, as a summary, due to the lack of "a generic like solution" right now
> some parts of the Go standard library might be less performant than they
> could be, even though the algorithms they use are solid, cause it's TOO
> much work to repeat yourself for each and every primitive type and make it
> an ah-hoc solution for those.
I think it's important to realize that there are different ways to
implement generics. In order to be able to use generics to make the
standard library much faster at runtime, you need to use an
implementation along the lines of C++ templates, in which each function
that uses a generic type is in effect rewritten to use that type and
then recompiled. This approach means that compilation slows down.
A different approach would be to, in effect, use the reflection
interface in the generic code. That would give you the programming
convenience, and keep the fast compilation times, without any large
runtime benefit. This approach is closer to (although certainly not the
same as) the way that C# or Java implement generics.
Or, in other words, http://research.swtch.com/generic .
Heck, if executables included the AST for the generic class, a hotspot compiler's jobs is much simplified, though boxing and unboxing is still a major pain at runtime.
I guess I don't see how. Two different packages might say
concrete func sortInts Sort<int>
When does that concrete function get compiled?
josvazg <jos...@gmail.com> writes:
> So, as a summary, due to the lack of "a generic like solution" right now
> some parts of the Go standard library might be less performant than they
> could be, even though the algorithms they use are solid, cause it's TOO
> much work to repeat yourself for each and every primitive type and make it
> an ah-hoc solution for those.
I think it's important to realize that there are different ways to
implement generics. In order to be able to use generics to make the
standard library much faster at runtime, you need to use an
implementation along the lines of C++ templates, in which each function
that uses a generic type is in effect rewritten to use that type and
then recompiled. This approach means that compilation slows down.
A different approach would be to, in effect, use the reflection
interface in the generic code. That would give you the programming
convenience, and keep the fast compilation times, without any large
runtime benefit. This approach is closer to (although certainly not the
same as) the way that C# or Java implement generics.
Or, in other words, http://research.swtch.com/generic .
Ian
the generic type would be compiled exactly once for each concrete type derived from it.
I think this is tending in the right direction: generic types would have distinct type metadata pointers (or integers). Once such pointers are created, generic functions need only have an additional pointer argument for each generic argument supplied, and the result is generic code that need only be compiled once, and is just as fast as ordinary go functions taking interface arguments (e.g. io.ReadFull, to pick a random example). With suitable optimizations, one should be able to write a generic Append that works *almost* as fast as the builtin append.I have some ideas in this direction, but it's waiting on time, and may never happen. I think a generics proposal for go will need to include a sample implementation in order to be taken seriously.
As I see it there are N purposes for generics:
On Tue, May 8, 2012 at 9:19 AM, Kyle Lemons wrote:
> This gets me thinking. If I have been following this correctly, what we
> want with generics is the way to have specialized code that runs without the
> overhead of type assertions when we know the concrete type. Perhaps this is
> something that can be done purely in the compiler and/or linker.
I don't see this at all as the role of generics. It's the syntax and
semantics that matter most, not efficiency. Currently there is no way
to write the append function in go, so it must be a builtin. I would
like to be able to write functions like append or copy. That is the
*primary* function of generics. If those functions are slow, it's not
the end of the world, but when you can't write them at all, that's not
so great.
As I see it there are N purposes for generics:
(A) Being able to write a function or method that could work a
container type regardless of what it contains (i.e. to write append or
copy). This would be useful for our existing container types: slice,
map or channel. e.g. you can't write a function that returns the
union of any two maps.
(B) Being able to define interfaces (and methods) that are
type-dependent, so a type-safe min(a,b) function could be written.
(C) Being able to write your own container types, i.e. if you wanted
to define a general red-black tree, or a linked list. (Requires A but
not B in order to be useful.)
(D) Being able to write type-safe functions involving multiple
arguments of a constrained type (but not a container type). e.g. to
write a function that returns the minimum of its two arguments.
(Requires B, but not A or C.)
(E) Being able to write interesting type-safe functions involving
containers (such as sort) in a type-safe way. (Requires A, B and D,
but not C.)
None of these uses for generics require that they be fast.
They do
require an extension to the language's type system. I'd be interested
to hear if there are any other uses for generics that people have
considered.
Of course, practically speaking, we'd also like generics to be fast to
compile and fast to run. Fast to compile I expect is a hard
requirement for go. Fast to run, I expect, is a softer requirement.
Go already has "slow" interfaces, which require method lookup from a
table for every method call, and no one (including myself) seems to be
concerned about that particular slowness.
(A) Being able to write a function or method that could work a
container type regardless of what it contains (i.e. to write append or
copy). This would be useful for our existing container types: slice,
map or channel. e.g. you can't write a function that returns the
union of any two maps.Unless I misunderstand you, it would seem like you could do this with reflection today.
(C) Being able to write your own container types, i.e. if you wanted
to define a general red-black tree, or a linked list. (Requires A but
not B in order to be useful.)Why aren't interfaces good enough here?
(E) Being able to write interesting type-safe functions involving
containers (such as sort) in a type-safe way. (Requires A, B and D,
but not C.)Maybe I'm missing the point, but it seems to me that the sort packages is already type safe. I.e. if you try to pass a value to sort.Sort() that doesn't implement sort.Interface you will get a compile time error.
A simple example of a function that you can't yet write would be to
write a Filter function, which takes a slice and a bool-valued
function of the element type, and returns a new slice with only those
elements that the function says are true. Or a lookup function, that
returns the first element of a slice that matches some criterion.
Thus E not possible in Go1, since you can't write the type of such a
function (except in simple cases where there is just one argument
involving the types of interest, and you don't mind forcing users to
typecast their container type to something else in order to satisfy an
interface).
On Thu, May 10, 2012 at 3:31 PM, Steven Blenkinsop <stev...@gmail.com> wrote:I agree about simple functions being better than more complicated
> Unless you follow the example of sort and use indices as references to
> elements which you then pass into type aware methods or closures. You still
> have to manually code the operations for each container type, but as I was
> saying, it's better to have to write a set of simple functions for each type
> that have low cognitive load than it is to write a more complex algorithm
> for each type that has high cognitive load.
ones, which is why I like the idea of enabling generics. I disagree
about generic functions (if generics are done right) having a higher
cognitive load than ordinary functions.
You're right that a Filter function could be written with the type:
func Filter(func(sort.Interface, int) bool, sort.Interface) []int
where the caller would then have to use the slice of indices to
construct the filtered slice. But this is sufficiently hard to use
that it would be pointless, because you could write the same function
in one line. So simple functions *can't* be written using the
sort.Sort approach, which is therefore only useful for for
*complicated* functions. But simple functions (like append and copy)
are *really* useful, so much so that we haven't been able to live (in
Go world) without generic versions of them. We can keep adding every
useful simple function to the language definition as a new primitive
(well, actually, we can't add any until Go 2), but that doesn't seem
like the best-scaling approach.
But simple functions (like append and copy)
are *really* useful, so much so that we haven't been able to live (in
Go world) without generic versions of them. We can keep adding every
useful simple function to the language definition as a new primitive
(well, actually, we can't add any until Go 2), but that doesn't seem
like the best-scaling approach.
I'd say that the sort.Sort approach is sufficiently complicated (and
has a sufficiently high cognitive load) that it's almost always the
wrong thing to do. But, of course, I'm coming with something of a
declarative bias, that functions should accept parameters as input and
return values (so they can be combined together in readable ways).
Yes. While certain foundations have been designed with orthogonality in mind, a tendency to grow the language by agglutination has been present since the beginning, as well. Introduction of `append' (which can operate only on slices) and `delete' (which can operate only on maps) illustrate it. Those built-in functions are also red flags: compiler magic is becoming necessary for some simple operations!
On 10 May 2012 23:15, JONNALAGADDA Srinivas:
> Why aren't interfaces good enough here?
How would I write the type of a linked list as an interface type? If
you can't specify its type, you can't declare data. Of course, with
reflection we could make all our arguments to functions be of type
interface{}, as well as the output of every function. But that isn't
particularly satisfying. e.g. imagine
type List struct {
Value interface{}
Next *List
}
func Head(list *List) interface{} {
if list == nil {
panic("head of empty list")
}
return list.Value
}
You need a manual type check on every access to a list element.
//A Link is any type with a Next method that returns a pointer to the next Link.type Link interface{
Next() *Link
}type IntLink struct{
int*Link nextLink
}func (i *IntLink) Next() *Link {
return nextLink
}
type IntLink struct{intnextLink *Link}