On Mon, 5 Nov 2018 at 02:35, Axel Wagner <axel.wagner...@googlemail.com>
wrote:

> On Sun, Nov 4, 2018 at 10:14 PM roger peppe <rogpe...@gmail.com> wrote:
>
>> This code, for example: https://play.golang.org/p/eaLHBbUJT-d
>> What might your generated code look like for that?
>>
>
> A bit ad-hoc, but WAI: https://play.golang.org/p/vuCHY2RRxIR
>
> In practice, of course, most of these wouldn't be actually generated
> exactly that way - instead you would use unsafe.Pointers and directly
> access the field of the rtype from the interface value. But it still show
> that it works.
>

So, if you're using unsafe.Pointer and directly accessing rtype fields,
where does that leave your assertion at the start of this thread that this
kind of code is "easy to devirtualize". The code you've written there
doesn't look exactly easy to analyse as it is, but start adding unsafe into
the mix and things get still harder.

BTW, about "You can't reference an uninstantiated generic function." -
you're right, that was a mistake. Suppose I'd written "save(sum(T))" there
instead of "save(sum)" though?


> If someone writes to the slice, you'd obviously pass more functions to it
>>> for those operations and the compiler can create appropriate closures to
>>> pass.
>>>
>>
>> That's the problem I have with the solutions you're suggesting. The set
>> of required operation arguments is open ended and depends on all the
>> generic data structures that are used within the function and all generic
>> functions that that are called by it.
>>
>
> As you can see above, all operations that are needed are either a)
> explicit in the contract, b) explicit in the type-constructors used in the
> function signature or c) are pretty universal and can be implemented via
> reflection.
>
> The code we've been using for an example is naively simple - any real code
>> would be much more complex. For example, your code is passing in a closure
>> that allows the code to get the index of one particular slice. But what if
>> the code creates another slice for itself? Presumably that would require
>> another operation function to be passed in. A slice of slices? That's
>> different and would require a different operation function, I think.
>>
>
> Sure, all of that is true. But… so what? I mean, yes, that is implied by
> the fact that we have a dynamic implementation of generics.
>

I don't think that's necessarily implied. I showed you a reflect-based
implementation that makes do with the operations provided by the reflect
package, which already has the operations needed. ISTM that as your
examples become more complex, the code trends more and more towards
reflect-like operations.

FTR, I am certain that most of these don't actually need to be passed - you
> should be able to get along with exactly one interface/itable per composite
> type in the signature and then use the passed *rtype to generate unsafe ops
> for the rest. But even if that's not the case - this is implicitly
> generated code, I don't see a problem with that, as both the list of
> operations necessary and the actually used itable have to be statically
> known. So this just comes down to passing a very large interface, with a
> statically allocated method table. It's not even expensive.
>
> What if it defines a new type and puts a value in it? Another operation.
>> Taking the address of a value, calling a function value, putting a value
>> into a map, copying a value, ...? All different operations requiring their
>> own entry points. As the complexity of the generic code rises, so does the
>> number of closures that must be created to allow that code to do all the
>> things that it needs to do. I don't see that as a viable implementation
>> strategy.
>>
>
> That's fair. We'll see how viable it is in practice - AFAIK something like
> that (though likely a lot more clever) is the plan, at least initially.
>
> Perhaps you might want to have a go at trying to translate a slightly more
>> substantial piece of generic code. For example, this generic implementation
>> of Dijkstra's algorithm: https://play.golang.org/p/BsktFSFVU0D. I'd be
>> interested to see what you end up with.
>>
>

i.e. I don't feel it's reasonable to expect me to manually translate ever
> more elaborate examples for you. There's an asymmetry of effort here - it
> is far easier for you to say "fine, you can translate X, but what about Y"
> than it is for me to do that translation. What I've done so far should IMO
> be enough to illustrate the feasibility and generalizability.


The reason I've been asking you to translate more elaborate examples is
because I'm not seeing an obvious pattern that associates the original code
with the you're suggesting might be generated. The  code you originally
suggested was this:

// contract adder(v T) { v + v }
func sum(x T, l int, get func(int) T, add func(T, T) T) T {
for i := 0; i < l; i++ {
x = add(x, get(i))
}
return x
}


and the final code (ignoring the save calls) was this:

func sum(s interface{}, lenT _len, getT _get, rangeT _range, newT _new,
addrS _addr, addT _add, derefT _deref) interface{} {
        save(x)
        save(s)
        save(addrS(s))
x := newT()
rangeT(s, func(_ int, v interface{}) bool {
addT(x, v)
return false
})
return derefT(x)
}


Even though all we did was save some references to values, the code has
radically changed (along with its runtime implications). The final code
doesn't represent the original code well. For example, using a callback
function to emulate a range means that labelled break/continue becomes
harder to do, as do defer semantics etc. The addrS function is taking the
address of a slice inside that function, not the address of the argument
slice to sum.

FWIW, here's your code written out slightly differently, with the arguments
put into an "instance" struct, and the save(sum(T)) call included. This is
similar to the style I've been been experimenting with here
<https://github.com/rogpeppe/genericdemo/blob/master/graph/graph-generated-p-p.go>

    https://play.golang.org/p/GX2AbQNb8-a

Note that we end up needing to generate an actual function stub with the
correct signature. That is, the compiler must generate this specifically
for each instance of the function. If it can do this, it could certainly
decide to generate inline code for it, which takes us right back to the
hill you decided to die on at the start of this thread, I think. :)

-- 
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+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to