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.