Language feature proposal: Anonymous interface implementation

2,856 views
Skip to first unread message

David DENG

unread,
Jul 7, 2013, 11:11:35 PM7/7/13
to golan...@googlegroups.com
The grammar looks like this:

data []int
sort.Sort(sort.Interface {
    Len: func() int {
        return len(data) 
    },
    Less: func(i, j int) bool {
        return data[i] < data[j]
    },
    Swap: func(i, j int) {
        data[i], data[j] = data[j], data[i] 
    }, 
})

One powerful point of this compared to defining a new type implementing the interface is that you can use closures.

How you guys think of this?
David 

Dave Cheney

unread,
Jul 7, 2013, 11:15:04 PM7/7/13
to David DENG, golang-nuts
How would this improve the readability of Go programs ? At first blush
there is now another way to satisfy an interface, and that sounds like
a step backwards.
> --
> You received this message because you are subscribed to the Google Groups
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to golang-nuts...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>

David DENG

unread,
Jul 7, 2013, 11:19:04 PM7/7/13
to golan...@googlegroups.com, David DENG
From my point of view, the above code is very easy to read. All implementation of the Interface are well organized together.

David

Dave Cheney

unread,
Jul 7, 2013, 11:22:34 PM7/7/13
to David DENG, golang-nuts
You missed the point of my comment. Your proposal adds an additional
way that types that satisfy an interface can be defined. This is an
overall negative for readability in my opinion.

Jesse McNelis

unread,
Jul 7, 2013, 11:26:47 PM7/7/13
to David DENG, golang-nuts


On 08/07/2013 1:11 PM, "David DENG" <david...@gmail.com> wrote:
> One powerful point of this compared to defining a new type implementing the interface is that you can use closures.
>
> How you guys think of this?

What happens when I do a type assertion on this? Or pass it to reflect?
What value and concrete type is it in a comparison or a type switch?
If I convert it to another interface, can I convert it back?

David DENG

unread,
Jul 7, 2013, 11:33:07 PM7/7/13
to golan...@googlegroups.com, David DENG, jes...@jessta.id.au
This is similar to a struct literal. The compiler may generate an internal type name that you cannot access.

You cannot assert/switch it to a concrete type. You can only use it with the interfaces it satisfies.

Converting between interfaces does not change the intrinsic type, so converting back is just OK.

David

Nigel Tao

unread,
Jul 8, 2013, 12:37:49 AM7/8/13
to David DENG, golang-nuts
On Mon, Jul 8, 2013 at 1:11 PM, David DENG <david...@gmail.com> wrote:
> How you guys think of this?

If you really find yourself writing a lot of sort.Interface
implementations, you can already use closures:
http://play.golang.org/p/J2D67PLYlg

Given that, I don't think we need to change the language.

David DENG

unread,
Jul 8, 2013, 12:42:21 AM7/8/13
to golan...@googlegroups.com, David DENG
So you have to define SortImpl, HeapImpl, XxxImpl, and so on... And the definition is some code providinng no information, just for satisfying the grammar. And you should not make any (typo) mistakes writing these definitions. Why not let the compiler do it?

David

mortdeus

unread,
Jul 8, 2013, 1:05:15 AM7/8/13
to golan...@googlegroups.com
This doesnt add anything to the language that cant already be done with a struct literal. 

Dan Kortschak

unread,
Jul 8, 2013, 1:06:43 AM7/8/13
to David DENG, golan...@googlegroups.com
Tests are good.

Nigel Tao

unread,
Jul 8, 2013, 1:56:56 AM7/8/13
to David DENG, golang-nuts
On Mon, Jul 8, 2013 at 2:42 PM, David DENG <david...@gmail.com> wrote:
> So you have to define SortImpl, HeapImpl, XxxImpl, and so on... And the
> definition is some code providinng no information, just for satisfying the
> grammar. And you should not make any (typo) mistakes writing these
> definitions. Why not let the compiler do it?

It's true that adding this language feature would have a benefit.
That's not the whole point. Any language change has a cost: the
specification needs to become bigger, the compilers need to become
bigger, you have to carefully consider any edge cases, you have to
consider any interactions with other language features (does exported
or non-exported names make a difference?), you have to make sure that
the grammar doesn't become ambiguous, and you have to anticipate
potential incompatibility with any future language changes (e.g.
generics) you would ever want to make. Language changes also mean that
some code works for Go 1.x but not for Go 1.y.

The bar for language changes is very high, and unlike the slice[i:j:k]
language change, this proposal doesn't add anything you couldn't
already do with the existing language, and the way to do that with the
existing language is the tens of lines of code it takes to write
SortImpl, HeapImpl and XxxImpl once. If you make a typo then you
should get a compile-time error.

As an analogy, the problem with C++ isn't a lack of features, or that
those features aren't beneficial.

If anything, I think you could argue for a sort.Funcs struct type that
is to sort.Interface what http.HandlerFunc is to http.Handler. A
standard library change has a lower bar to meet than a language
change. But I'm still not sure if that would get accepted (gri owns
that package), and once again, it would take less time to add the 20
lines to your program then I've already spent on this e-mail.

David DENG

unread,
Jul 8, 2013, 3:03:52 AM7/8/13
to golan...@googlegroups.com
If so, OO should have never existed, since it can be totally done with function pointers.

There are so many things the compiler has done for the coder. Using a struct literal to simulate this is very boring and easy to generate bugs.

David

Jan Mercl

unread,
Jul 8, 2013, 3:35:08 AM7/8/13
to David DENG, golang-nuts
On Mon, Jul 8, 2013 at 9:03 AM, David DENG <david...@gmail.com> wrote:
> If so, OO should have never existed, since it can be totally done with
> function pointers.

So called "OO" programming, is basically language neutral. You
literally _can_ use objects in machine code (true story). That said,
the "should have never existed" claim is IMO not fact based.

> There are so many things the compiler has done for the coder. Using a struct
> literal to simulate this is very boring and easy to generate bugs.

Would you mind to provide an example for "easy to generate bugs" and
how you proposal, in contrast, avoids the same?

-j

David DENG

unread,
Jul 8, 2013, 3:47:28 AM7/8/13
to golan...@googlegroups.com, David DENG
e.g.:

http://play.golang.org/p/1Z0uXVzdG3

The compiler won't fail for every typo mistakes.

David

minux

unread,
Jul 8, 2013, 4:17:41 AM7/8/13
to David DENG, golan...@googlegroups.com
On Mon, Jul 8, 2013 at 3:47 PM, David DENG <david...@gmail.com> wrote:
e.g.:

http://play.golang.org/p/1Z0uXVzdG3

The compiler won't fail for every typo mistakes.
true, the compile won't ever catch all your typos (or it must be able to read your mind).
for example, compiler also won't save you from mistakenly calling Read instead of Write
on an os.File.

those errors should be caught by testing.

David DENG

unread,
Jul 8, 2013, 4:25:00 AM7/8/13
to golan...@googlegroups.com, David DENG
Ok. My point is: one more line you have to write, one more chance you make a bug. 

So if something can be done by machine, just let it be. If the machine can help me only if he reads my mind, you have to handle it yourself. And you write tesecases.

David
Message has been deleted

Zizon Qiu

unread,
Jul 8, 2013, 5:19:03 AM7/8/13
to David DENG, golang-nuts, jes...@jessta.id.au
it is neither a `type` nor an interface?



--

David DENG

unread,
Jul 8, 2013, 7:19:06 AM7/8/13
to golan...@googlegroups.com, David DENG, jes...@jessta.id.au
It's an anonymous concrete type implementing a specific interface.

David

krolaw

unread,
Jul 8, 2013, 7:23:20 PM7/8/13
to golan...@googlegroups.com, David DENG, jes...@jessta.id.au
Just a suggestion.

Could field functions someday be accepted as interface methods?


would kind of meet David's request without changing the language structure, but I have no idea what the implications would be?

Cheers.

David DENG

unread,
Jul 8, 2013, 7:56:11 PM7/8/13
to golan...@googlegroups.com, David DENG, jes...@jessta.id.au
This is a good suggestion. +1

David

mortdeus

unread,
Jul 8, 2013, 10:55:14 PM7/8/13
to golan...@googlegroups.com
The point of Go is to try and keep the language's complexity as minimal as possible. We want a simple language because it enables developers to

A. debug and understand the code were reading, especially other people's code; comfortably..
B. easily memorize the entire language's grammar and standard library.
C. be able to comprehend the compilers source. Focusing on keeping things simple in the language's grammar resulted in a compiler that is easier to debug, experiment with, and extend. Every feature that we add to the language takes us one more step towards C++.

Before we add new syntax to the grammar we have to be absolutely sure that the only features we add are the features that feel really idiomatic. And not only from an aesthetic perspective but from an engineering one too. The thing is, while we appreciate your enthusiasm in Go, your proposal doesnt really bring anything to the table that cant already be done in a more useful way. (albiet longer.)  (http://play.golang.org/p/OphGvoGWkh)



On Sunday, July 7, 2013 10:11:35 PM UTC-5, David DENG wrote:

David DENG

unread,
Jul 9, 2013, 12:26:32 AM7/9/13
to golan...@googlegroups.com
All the reasons are right, and understandable, the problem is the threshold.

For the code you provide, I want to saym it is not the case I commonly face using sort.Interface.

The elements are commonly a slice of a custom struct, like []Foo. Or sometimes to avoid data moving, I may have an int slice as the indexes, then sort on indexes. Either way, the slice does not satisfy the sort.Interface. And in the latter situation, the less function is not a trivial one, but the one comparing corresponding element in the data slice(it is a closure). some code fragment explains better: http://play.golang.org/p/UNlf8tXdIc 

SortF is defined in some package, but it only works for sort.Interface, my proposal can make the code still simple without defining SortF, i.e. works for any interface.

Actaully, I have been designing the exported functions with a sequence of parameter or function pointers (like SortF), instead of an interface, for convenience of using it.

David

Nigel Tao

unread,
Jul 9, 2013, 12:33:54 AM7/9/13
to David DENG, golang-nuts, Jessta
On Tue, Jul 9, 2013 at 9:56 AM, David DENG <david...@gmail.com> wrote:
> This is a good suggestion. +1
>
> On Tuesday, July 9, 2013 7:23:20 AM UTC+8, krolaw wrote:
>> Just a suggestion.
>>
>> Could field functions someday be accepted as interface methods?
>>
>> Then: http://play.golang.org/p/EMSLcTwLCx

The suggestion is to allow something like:

--------
type T struct {
Len func() int
Less func(i, j int) bool
Swap func(i, j int)
}

func main() {
data := []int{0, 3, 4, 1, 5, 2}
sort.Sort(T{
func() int { return len(data) },
func(i, j int) bool { return data[i] < data[j] },
func(i, j int) { data[i], data[j] = data[j], data[i] },
})
fmt.Println(data)
}
--------

It's cute. I can see the appeal.

However, specifically for David Deng, saying "This is a good
suggestion. +1" is not helpful. The benefits are fairly clear, but I
don't think that you appreciate the costs, and repeating that it's
beneficial is not helpful.

Language changes must work well with all the other language features,
and ideally be orthogonal to other features (and not require "special
cases"). For example, the language specifciation defines a concept
called the "method set" of a type. What is the method set of T? Is it
empty, or does it contain Len, Less and Swap.

If empty, then T cannot implement the sort.Interface interface,
because interface satisfaction is defined in terms of method sets.

If T is not empty, then T has methods even though there is no code
anywhere that starts with "func (t T) MethodName". Other than struct
embedding, implicit methods would be a new feature of the language.
Does this break the language assumptions of any existing tools?

If T is not empty, then T both has a field named Len and a method
named Len. T having two things with the same name would be a new
aspect of the language. What exactly do I mean by T.Len? Is it the
field or the method? What is the type of x if I say x := t.Len? The
two candidates (field or method) are very similar (they are both
callable), but possibly subtly different. Does this break any
assumptions of the reflect package? Will this ever have regrettable
implications for any future language or standard library changes we
want to do with interfaces, structs, method sets, method variables,
func types, the reflect package API, the reflect package
implementation, compiler implementations, tool support (including
IDEs), etc.

If you do make this change, in existing code that says
type Foo struct {
String func()
}
Foo now has a String method, where it did not have one before. Foo now
implements fmt.Stringer where it did not before. Calling
fmt.Printf("%s", foo) could now infinite-loop, where it did not
before. Is this only a theoretical problem, or will this break
existing code? How do you know?

To repeat myself: the benefit to cost ratio for language changes needs
to be higher than other changes. I don't think you have considered all
the potential costs, and to be frank, the benefit seems to be that you
can avoid writing the 9 lines of code below in your project (write it
once, use it as many times as you want). Even if you make a typo in
those 9 lines, and even if it's one that the compiler doesn't catch,
it's still not hard to find and fix.

--------
type SortFuncs struct {
LenFunc func() int
LessFunc func(i, j int) bool
SwapFunc func(i, j int)
}

func (s SortFuncs) Len() int { return s.LenFunc() }
func (s SortFuncs) Less(i, j int) bool { return s.LessFunc(i, j) }
func (s SortFuncs) Swap(i, j int) { s.SwapFunc(i, j) }
--------
Message has been deleted

David DENG

unread,
Jul 9, 2013, 12:46:35 AM7/9/13
to golan...@googlegroups.com, David DENG, Jessta
For the sort.Interface example, how to make closures satify the interface in a simple way? 

Is it ok allowing local method definition? like:

func main() {
    data := []Foo{};
    type Indexes []int
    func (idx Indexes) Less(i, j int) bool {
        return data[idx[i]].Score < data[idx[j]].Score
    }
}

David

Nigel Tao

unread,
Jul 9, 2013, 12:47:42 AM7/9/13
to David DENG, golang-nuts, Jessta
On Tue, Jul 9, 2013 at 2:46 PM, David DENG <david...@gmail.com> wrote:
> For the sort.Interface example, how to make closures satify the interface in
> a simple way?

Here's the example I gave yesterday:
http://play.golang.org/p/J2D67PLYlg

Rob Thornton

unread,
Jul 10, 2013, 12:16:16 PM7/10/13
to golan...@googlegroups.com
I want to weigh in on this conversation from a non-professional point of view. Unlike several others who are far more qualified to speak on the technicalities of implementing a feature like this I have to respectfully agree with them. I like Go because it is simple and doesn't have constructs like this. Go is an elegant language because it doesn't have fancy tricks to remove a few lines of code. To me, as others I think have already said, this feature doesn't bring anything new to Go it just provides a prettier way to do it. An existing example of a feature like this are the multiple ways of declaring variables. We can:

var a int = 1
var b = 2
var c = int(3)
d := 4

Four ways to do the same thing. To me, this proposal is just another way to do something we can already do with just a couple fewer lines. This is specifically one of the reasons I don't like other languages which have multiple ways of doing the same thing, each more obfuscated than the last.

Rob


On Sunday, 7 July 2013 20:11:35 UTC-7, David DENG wrote:
The grammar looks like this:

data []int
sort.Sort(sort.Interface {
    Len: func() int {
        return len(data) 
    },
    Less: func(i, j int) bool {
        return data[i] < data[j]
    },
    Swap: func(i, j int) {
        data[i], data[j] = data[j], data[i] 
    }, 
})

Arne Hormann

unread,
Jul 10, 2013, 12:24:31 PM7/10/13
to golan...@googlegroups.com
Don't forget d := int(4), I really like that one!

David DENG

unread,
Jul 10, 2013, 7:34:26 PM7/10/13
to golan...@googlegroups.com
I'm not sure about why you mention the ways to declaring variables, you like Go because of this? Or this is the only thing you dislike about Go?

Actaully, there are 6 ways:

var an int
b := int(2)
c := 3
var d = int(4)
var e = 5
var f int = 6

Commonly, I only use the first 2 ways, depending on whether it requires an intial value or not.

David

David DENG

unread,
Jul 10, 2013, 7:49:26 PM7/10/13
to golan...@googlegroups.com
What do you guys think of the features of dynamic interface, with this supporting, I can do rest without languange supporting, along with many other requirements.

dynamic interface looks like this:

a built-in function named forge (or something else), which accepts a list of functions (and their names) as motheds of the returned instance:

v := forge(
    Len, func() int { ... },
    Less, func(i, j int) bool {...},
    Swap, func(i, j, int) { ... });

sort.Sort(v.(sort.Interface)) 

RPC can be done with much more elegant way. I can write an interpreter of Go with this.

This introduces no new grammar, just a new built-in.

David

David DENG

unread,
Jul 10, 2013, 7:53:21 PM7/10/13
to golan...@googlegroups.com
Sorry, Len/Less/Swap should be strings: "Len", "Less", "Swap".

Besides RPC, json/gob's marshal/unmarshaling can be done much more efficient for unified data, since reflection is only used in intialization stage.

David

Kyle Lemons

unread,
Jul 10, 2013, 8:20:46 PM7/10/13
to David DENG, golang-nuts
On Wed, Jul 10, 2013 at 4:53 PM, David DENG <david...@gmail.com> wrote:
Sorry, Len/Less/Swap should be strings: "Len", "Less", "Swap".

Besides RPC, json/gob's marshal/unmarshaling can be done much more efficient for unified data, since reflection is only used in intialization stage.

gob already uses reflection in init and builds a machine.

The reflection library could theoretically be modified (with appropriate runtime support) to do what you describe without a language addition.  Something akin to

import "reflect"

// NewStructType creates a new struct type with the given fields and methods.  All fields and methods must be exported.
// The name must be unique among all dynamically constructed types.
func NewStructType(name string, fields []reflect.StructField, methods map[string]reflect.Value) reflect.Type
 

--

Nigel Tao

unread,
Jul 10, 2013, 8:25:29 PM7/10/13
to David DENG, golang-nuts
On Thu, Jul 11, 2013 at 9:49 AM, David DENG <david...@gmail.com> wrote:
> What do you guys think of the features of dynamic interface,

It's not a direct answer to your question, but PHP continually
accumulated features, each one useful to someone at the time, and the
end result was http://me.veekun.com/blog/2012/04/09/php-a-fractal-of-bad-design/


> RPC can be done with much more elegant way. I can write an interpreter of Go
> with this.
>
> Besides RPC, json/gob's marshal/unmarshaling can be done much more efficient
> for unified data, since reflection is only used in intialization stage.

I'm presuming that RPCs becoming more elegant means that you can use
closures, but I don't find that compelling over what we already do
today, implementing interfaces. I've written and reviewed a lot of Go
code that uses RPCs, and I've never wanted closures.

Why can't you already write a Go interpreter? What is "unified data"?
What's your basis for claiming that it makes it more efficient?

Richard Warburton

unread,
Jul 10, 2013, 8:33:00 PM7/10/13
to Nigel Tao, David DENG, golang-nuts, Jessta
Hi Nigel,

You're absolutely right.  I have no idea of the implications.  I appreciate your effort detailing some of the pitfalls of such a change, especially when you're probably deluged by language suggestions.

Cheers.


David DENG

unread,
Jul 10, 2013, 8:50:35 PM7/10/13
to golan...@googlegroups.com, David DENG
RPC (I mean "net/rpc") can be designed in this way:

// shared 
type Adder interface {
    Add(a, b int) int
}

// server
type RealAdder struct {}

func (a RealAdder) Add(a, b int) int {
    return a + b
}

server := RpcServer(&RealAdder{})

// client
client := RpcClient(remoteAddr).(Adder)

c := client.Add(a, b)

David

Jesse McNelis

unread,
Jul 10, 2013, 9:01:02 PM7/10/13
to David DENG, golang-nuts
On Thu, Jul 11, 2013 at 10:50 AM, David DENG <david...@gmail.com> wrote:
RPC (I mean "net/rpc") can be designed in this way:

// shared 
type Adder interface {
    Add(a, b int) int
}

// server
type RealAdder struct {}

func (a RealAdder) Add(a, b int) int {
    return a + b
}

server := RpcServer(&RealAdder{})

// client
client := RpcClient(remoteAddr).(Adder)

c := client.Add(a, b)

There is a really good reason why RPC doesn't work like this.
Networks are never transparent.

 
--
=====================
http://jessta.id.au

David DENG

unread,
Jul 10, 2013, 10:00:03 PM7/10/13
to golan...@googlegroups.com, David DENG, jes...@jessta.id.au
You can require the last return value must be an error, for reporting of network failure.

David

Kyle Lemons

unread,
Jul 10, 2013, 10:05:39 PM7/10/13
to David DENG, golang-nuts, Jesse McNelis
On Wed, Jul 10, 2013 at 7:00 PM, David DENG <david...@gmail.com> wrote:
You can require the last return value must be an error, for reporting of network failure.

Except that reports the errors out of band.  What do you return from Add when the network fails?
 
David

On Thursday, July 11, 2013 9:01:02 AM UTC+8, Jesse McNelis wrote:
On Thu, Jul 11, 2013 at 10:50 AM, David DENG <david...@gmail.com> wrote:
RPC (I mean "net/rpc") can be designed in this way:

// shared 
type Adder interface {
    Add(a, b int) int
}

// server
type RealAdder struct {}

func (a RealAdder) Add(a, b int) int {
    return a + b
}

server := RpcServer(&RealAdder{})

// client
client := RpcClient(remoteAddr).(Adder)

c := client.Add(a, b)

There is a really good reason why RPC doesn't work like this.
Networks are never transparent.

 
--
=====================
http://jessta.id.au

David DENG

unread,
Jul 10, 2013, 10:44:48 PM7/10/13
to golan...@googlegroups.com, David DENG, Jesse McNelis
Zero values for normal values, and the network error at the last.

David

Kyle Lemons

unread,
Jul 11, 2013, 2:25:50 PM7/11/13
to David DENG, golang-nuts, Jesse McNelis
On Wed, Jul 10, 2013 at 7:44 PM, David DENG <david...@gmail.com> wrote:
Zero values for normal values, and the network error at the last.

Yeah.  You don't want that.  This makes it impossible to tell if a zero you get back was from an error or the real answer.  I think it would be wrong to even consider letting Add(1, 1) return 0.  The only remedy you have is a panic, but you know how good a solution that is ;-).

Ross Light

unread,
Jul 11, 2013, 2:28:07 PM7/11/13
to David DENG, golang-nuts
It is straightforward to write a code generator to provide type-safe RPC from a shared interface.  This has the advantage of being able to use net/rpc and does not require language changes.

Ross Light | Software Engineer | li...@google.com | 661-375-7677


--

David DENG

unread,
Jul 11, 2013, 7:02:10 PM7/11/13
to golan...@googlegroups.com, David DENG, Jesse McNelis
If Add is defined in this way:

Add(a, b int) (int, error) 

It's doesn't matter what is returned if error is non-nil. This is idiomatic go style.

David

David DENG

unread,
Jul 11, 2013, 7:08:38 PM7/11/13
to golan...@googlegroups.com, David DENG
We have to use net/rpc and cannot change the language. But I think it is neither convenient nor straightforward. Maybe, all RPC in Google is in protocal buffer, which has to be done in this way, no matter in whatever languange. But I used some other RPC framework in Java, which does not need a code-generator stuff. I change the code, and I compile it, that's all.

I've made a try on definition of RPC ( https://github.com/daviddengcn/go-rpc ), which does not need input and output to be a single parameter. A little better for me.

David
Reply all
Reply to author
Forward
0 new messages