On Sat, Jan 13, 2018 at 9:32 AM, Peter Mogensen <a...@one.com> wrote:

>
>
> On 2018-01-13 01:35, Axel Wagner wrote:
> > But the answer to 2 is "no, not in a type-safe way". It necessarily
> > requires reflection. Even if we'd assume we somehow magically know that
> > the dynamical value of the interface is some pointer type. That doesn't
> > actually help us, in any reasonable way. We can dereference it, but we
> > wouldn't know what the thing it's pointing to is. Or to put it another
> > way: To dereference a pointer, you'd have to know - at the very least -
> > the size of the thing it's pointing to.
>
> Which we do. It's a pointer.


> > And to do it in a type-safe way,
> > you'd have to know the /type/ of the thing it's pointing to.
>
> Or just ensure the type T is the same for *T as it was for **T
> I can't see that's ambiguous in the common language sense of the word.
>
> > Yes, in theory, the compiler an inspect the type-info of the interface,
> > see that it's a pointer-type and then construct a new interface-value,
> > with dynamic type "type of the pointee" and dynamic value "pointee" -
> > that's reflection. I.e. the compiler would emit code for the equivalent
> of
> > v := reflect.ValueOf(v).Elem().Interface()
> > That is the best you can do, but in particular, you don't have any
> > static information about what the value is you are operating on, so you
> > can't call any methods on it, you can't dereference it, you can't do…
> > anything with it, really. Like, you can't actually do any better than
> > what reflection gives you - and reflection already works.
>
> My point about that the dereference operation was the only meaningful
> one was exactly because there's no static type info. As I said you
> probably can't write a function, but a language *could* in theory define
> an operator which would turn any **T into a *T, regardless of type.
>

Go *has* this operator, it is spelled "*" and is a unary operator with even
more power: It can turn a general *T into a * for any T.

Say, you'd have the function you want, called, for example "join". There
are two questions we have to answer: a) How does it interact with the type
system and b) how is it implemented (i.e. what are the instructions emitted
by the compiler).

b) is simple to answer. The code emitted for join would be equivalent to
this:
// join assumes q is a pointer to a pointer and returns it's dereference
func join(q unsafe.Pointer) unsafe.Pointer {
    return *(*unsafe.Pointer)(q)
}
Now, this is using unsafe, but that's functionally equivalent to other
generic functions (e.g. append, copy…) in that the compiler checks the
types for consistency and then emits the corresponding call. So, the unsafe
would be hidden by the language. It would seem to me, your question about
"would it be ambiguous" is "can this function be generated" and the answer
is, of course, yes.

a) is about how to expose this to the language, and is a bit harder. It
depends on whether you have parametric polymorphism in your type system or
not.

If you don't, then `join` can not have any sensible type; it's type would
be func(**T) *T, which, is parameterized. So join would have to be
"magically generic", like append/copy/… which don't have a type, because
they are builtins and can't be passed around. But then, this has no
advantages over a simple dereference. You can't use it as a function, you
always have the full types available statically, so you can also just write
p := *q.

If you *do* have parametric polymorphism, you can of course write this
function (as you've demonstrated with C++). Note, however, that if you have
full-blown generics, you will likely need to add this as an optimization. A
priory, type-parameters can have different sizes, so the compiler would
need to figure out, that if you write that parametric type, all types that
you can instantiate it with, will have the same size and thus it can reuse
the code.

But note, however, that this is still not the same as writing a func(v
interface{}) interface{}. Because the type of that function, by definition,
boxes its value and can thus be called with non-pointers (or has to assume
it could be). And thus has to manually verify the types it gets passed via
reflection.

So, you end up with
1) a magically generic join, expanded by the compiler. For this to work,
the compiler needs to have the full type-information available and this is
thus a functional subset of normal pointer-dereference
2) a join written using compile-time generics. This is the C++ case you
mention.
3) a join written using run-time generics. This is reflection.

You seem to, essentially, be asking about whether you can have parametric
polymorphism in the language, but *only* for multi-level pointers. Which… I
mean, sure, you can do that, but it doesn't seem a sensible question to me.
But, yes, if you'd allowed to parameterize over pointers, you can have
something like

func<P> join(q *P) P {
    return *q
}

(making up some syntax for "P is some generic pointer type", i.e. P = *T
for some T). And yes, given that all type-parameters will always have the
same representation, you can always check and expand this at compile-time
(i.e. no boxing whatsoever required) and wouldn't need to use any
reflection. It just seems like a ridiculously useless language feature to
me. It *would* allow you to write *some* generic type-safe containers, as
long as you don't want to store the value inline. But you couldn't write,
e.g. a generic hash table (as the hash-function would need to take the
actual type) without *also* making up some kind of notion of covariant
subtyping. So you could, e.g. write something like

func<P> NewTable(hash func(P)) *Table<P>

and instantiate this with

table := NewTable(func(p *MyType) { return hashMyType(p) })

But that's yet another problem.


So in summary: The answer to your question is "yes it's possible", but the
result will still either a) be a subset of the dereference operator or b)
require parametric polymorphism and c) potentially only for pointer-types,
which would seem mightily useless.


> > Your request is essentially equivalent to reflection as a
> > language-feature, which is essentially equivalent to making Go a
> > dynamically typed language.
>
> It was not a feature request.
> I just posed the question out of curiosity. The rest of the discussion
> has been about whether it was ambiguous.
> If a language (in theory) provided an operator which would only act on
> variables of type Pointer-To-Pointer-To-T and returned the Pointer-To-T,
> I can't see how that would be ambiguous. And it could still do the same
> level of static type checks as Go does with interfaces.
>
>
> >     template<class T>
> >     T *f(T **p) {
> >         return *p;
> >     }
> >
> >
> > This is not the same. In the case of this C++ code, the actual type of T
> > is statically known.
>
> I know .. I said it was cheating.
> My point with that example was to say that it's not ambiguous (*in the
> common language sense of the word*) what is meant.
> You can state it and there's no doubt what is meant.
>
> /Peter
>

-- 
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