"interface{} says nothing", lets consider it destroys information

442 views
Skip to first unread message

mhh...@gmail.com

unread,
Jul 17, 2017, 5:07:46 AM7/17/17
to golang-nuts
in func do(i interface{}) interface{} (return i), do says nothing because "interface{} says nothing",

from the caller pov, it looses information at the return call,

var x = "o"
do(x) // <- return lost the track it was a string

if you delegate that func to another func,
it can t says anything else rather than nothing unless it introduces type assertion.
func to(do func(interface{})interface{}) func (interface{}) interface{} { return func (i interface{}) interface{} {return do(i)} }

if the observation become "interface{} destroys information",
does it make sense to consider a "value type of any type that carries out its input type" ?
func do(any <t>) <t> {return any}
do("thing") <- this is explicitly string

Acting the same way as interface, except that little thing about destroying/preserving an information.

It can be delegated to func that works on anything, still returns concrete types.
func to(do func(<t>)<t>) func (<t>) <t> { return func (i <t>) <t> {return do(i)} }

to(do)("whatever") // got a string, right ?

One step forward,
what if <t> might be able to say "any type with a constraint on that interface",
func do(any <t:Stringer>) <t> {return any}
do(WhateverStringerCapable{}) <- much like using a regular parameter of type Stringer/interface{}, but now the return call reflects the invoke call , so its more complete than interface{}/Stringer.

no?

Or maybe there is a reason in destroying that information ?

Eric Johnson

unread,
Jul 19, 2017, 5:47:40 PM7/19/17
to golang-nuts
While I lean towards the view that Go should add support for type generics, I'm not sure your example actually provides sufficient detail to be an argument for them.
If the "do" method takes and returns a Stringer, then why not just declare it that way? To make this a more interesting discussion, you have to get into the details of what the "do" function actually needs to do? Why can't it just use standard interfaces?

As I see it, one of the generics problems comes from using the built-in slices and maps. As it current stands, if I create a method:

func concat(foo []Stringer) String {
    result
= ""
   
for _, s := range foo {
        result
= result + s.String() + ";
    }
    return result
}

but suppose I have two structs, Foo, and Bar, and both *Foo, and *Bar implement Stringer.

I cannot do this:
func myFunc(f []*Foo, b []*Bar) string {
    return concat(f) + concat(b)
}

This seems like a more concrete scenario than the one you identified.

Eric.

mhh...@gmail.com

unread,
Jul 20, 2017, 12:21:21 AM7/20/17
to golang-nuts
I think your example is not relevant, as it clearly intend to change the input type,
the goal is to preserve it, while still working with its value.


interface{} value type destroys the input type information,
so you might have the opposite value type,
a type that preserves.
A type that let you write, func, please, return the type i got.

I did not mention templating, code generate / whatever/  like in https://en.wikipedia.org/wiki/Generic_programming

I read something related in "Generic programming is a style of computer programming in which algorithms are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters."

...to-be-specified-later... runtime leaks in the static type system, mostly a question of pov.

- <t> behaves like interface{} value type, because it does still say absolutely nothing. Not because it lacks of identity, but because it represents too many possible type.
- <t:Interface> is a shorthand to avoid a, probably, very repetitive type assert in order to use a <t>, much like an interface{}, but better, because its shorter
- <t> can be use only in func/method parameter (excluded receiver) ? I have not been able to find it meaningful elsewhere.
- if you don t need to return the input type, you don t need <t>
- ultimately, []interface{} == []<t>, they both are list of stuff we have no idea what its about, but <t> has no sense if it is not scoped to a function call stack.
- ultimately, []<t:Stringer> does not make sense.

- when you receive a func (x <t>) <t> {}, it does actually says nothing to you, the declarer of the func, not because it lacks of identity, but because it represents too many possible types. If you d want to do something of it, you need to type assert it, to narrow it to something meaningful.
- when you receive a constrained <t:Interface>, you can work on value of type Interface, and you can return whatever, including the input parameter type
- when you call a func with a constrained type, you can actually statically verify that the input value satisfies the constraint.
- when you call a <t>, anything is as good as nil (i guess)

mhh...@gmail.com

unread,
Jul 20, 2017, 12:32:48 AM7/20/17
to golang-nuts
...I have not been able to find it meaningful elsewhere.

sorry, i forgot,

- ultimately, type D struct {r<t:Interface>}, does not make sense either imo.

prade...@gmail.com

unread,
Jul 20, 2017, 9:40:00 AM7/20/17
to golang-nuts
Go could have least have parametric functions (ex :

func Foo<T>(value T)T { /.../ }

bar
:= Foo<int>(3) //types are verified at compile time, no need for reflection or interface {} on any runtime trick.

).

But talking about this is kind of useless until Go rids itself of its over reliance on runtime features like reflection. I suspect Go reflection capabilities are why the language stopped evolving significantly at first place. String tags are another symptom of that "disease".

It's possible to write the snippet above with reflection today (reflect.MakeFunc) , it shouldn't be.


mhh...@gmail.com

unread,
Jul 20, 2017, 11:07:02 AM7/20/17
to golang-nuts
In func Foo<T>(value T)T { /.../ }

It makes sense to anchor the definition of T to the func name.

I feel like it is not needed, and i wonder about multiple T parameters.

Where the question here is about

func Foo(x <t>, y <u>) (<t>, <u>){}
Or
func Foo<t,u>(x t, y u) (t, u){}


At least, it is possible to say your version has more impact on the overall syntax than my initial proposal,
because using your version there will be new things like


bar
:= Foo<int>(3)


In my proposal, because 3 is an int, <t> is solved.


another very questionable thing,
if you have <t:string> (basic type), you can return only a (string), or destruct it into another type (strconv.Atoi).
that happens because string does not match any interface, its basic.
So f(<t:string>) seems totally useless and redundant to f(string).

<t:Constraint>  is really interesting with structs and interfaces,
because struct can have as many facets as there is interface it satisfies.
- its open for the caller, you can inject and receive any type that satisfies the contract
- closed to the declarer, it must become a meaningful type before you actually interact with it


________________

Aside of this, i have lots of troubles to write the clear distinction between interface{} and interface.
I d really like we had another keyword like contract to define an interface.
or empty{} rather than interface{}.

Anything that get rides of this naming problem.

Eric Johnson

unread,
Jul 20, 2017, 1:36:27 PM7/20/17
to golang-nuts
Again, I'm mostly for generics being added to Go, because I've found occasion where they would be useful...


On Thursday, July 20, 2017 at 6:40:00 AM UTC-7, M P r a d e s wrote:
Go could have least have parametric functions (ex :

func Foo<T>(value T)T { /.../ }

bar
:= Foo<int>(3) //types are verified at compile time, no need for reflection or interface {} on any runtime trick.

).

To get at the value of adding generics, knowing what "Foo" is doing is important. Presumably, the function operates on "value" in some way.

Can we say that it implements an interface? Then let's suppose the existence of an interface "X". I can currently rewrite your "Foo" function as:

type X interface {
   
Clone() X // documentation needed to clarify that the clone is really of the derived type implementing X.
   
DoFoo()
}

func
Foo(value X) X {
   result
:= value.Clone()
   result
.DoFoo()
   
return result
}

Now supposing I have a struct B, where *B implements "X", and is currently in variable "orig".

When calling this, to reflect that the result of Foo is still of type *B, the following is required.
   fooClone:=(*B).(Foo(orig))

What I want to be able to write, and have *B as the type of "clone":
   fooClone:=Foo(orig)

What we need the function signature to specify, then, is two pieces of information, that the parameter "value" is of the same type as its return, and that it implements interface "X". Also, my intuition suggests distinguishing generic types from concrete ones syntactically, so add an indication that's true. Perhaps try a few (+, ^, %).

A bunch of options that all get at the same idea, using different characters and different approaches:
func Foo(value T^->X) T^
func Foo(value T%:X) T%
func Foo(value T+:X) T+
func Foo<T^->X>(value T^) T^
func Foo<T%->X>(value T%) T%
func Foo<T+->X>(value T+) T+

where the "->" could really be anything syntactically appropriate, but what it means is "T" implements X. Again, not sure of the right syntax, and not sure this is required.

In the "X" interface itself, we have a slightly different problem for the "Clone" method. We need someway to indicate "the concrete type of the thing". For that, we use the syntax indication of a generic type (^, %, or +, above) appended to something. "@" seems like a natural fit. That probably needs to look something like this:

Clone() @^

Putting it all together, choosing my favorite representation from above:
type X interface {
   
Clone() @+
   
DoFoo()
}

func
Foo(value T+:X) T+ {
   result
:= value.Clone()
   result
.DoFoo()
   
return result
}

Now, I can write what I wanted to write:

fooClone := Foo(orig)

There's a completely separate question as to whether the compiler generates code that is effectively of the form:
fooClone := (*B).(Foo(orig))

I'm not sure I care about that. I'm more concerned about the correctness of the code.

Eric.

Jesper Louis Andersen

unread,
Jul 20, 2017, 4:25:30 PM7/20/17
to mhh...@gmail.com, golang-nuts
On Mon, Jul 17, 2017 at 11:07 AM <mhh...@gmail.com> wrote:
does it make sense to consider a "value type of any type that carries out its input type" ?

Yes. This is called a "universal" and is related to the concept of parametrization by J. Reynolds.

Your 'do' function is often called 'id' for the obvious reason that it is the identity function. In SKI logic it is the I combinator. If we annotate the type as you would in type theory, you would write something like

id : (T : Type) -> T -> T
id t x = x

to say that the function can be instantiated with any type 'T' you desire. The actual implementation of 'id' takes two parameters. First it takes the desired type and then it takes the parameter and returns it. Writing 'id Int' is a partial invocation. It "plugs in" the T and yields a function of type 'Int -> Int'. Likewise, 'id String' plugs in strings in the position. Interestingly, Reynolds showed that if the function 'id' is to work for *any* type T at the same time, it *must* have the above implementation. No other implementation is valid. This is the concept of parametrization. Even better, the type T can be a type outside of the type system of the programming language!

But do note there is no way a function such as 'id' can manipulate the contents in any way. It is allowed to pass a (generic) data value, but it is not allowed to scrutinize said data value at all.

The next question is if you can add constraints to the type. In particular, you want access to the stringer interface in order to grab a string representation of the values. That is, you want to allow any type T for which it holds that it is a member of the Stringer interface.

exclaim : Show a => a -> String
exclaim x = show x ++ "!"

The exclaim function is one such function example. It accepts any type 'a' for which the "Show" interface is implemented. Then it prints the value and adds an exclamation mark at the end of the string. It works for any type implementing the "Show interface".

The above code works in Idris, or Haskell with minor modifications, so the generality is definitely doable.

The price you pay for these types of generalizations tend to be compilation time or slower execution time. It is one of the places where I tend to disagree with the Go authors: I think the added compilation time is worth paying for the added expressive power in the language. I also think you can make compilation of generics really fast, so the added compilation time is somewhat manageable (and only paid if you actually invoke a generic construction).


 

mhh...@gmail.com

unread,
Jul 21, 2017, 4:32:21 AM7/21/17
to golang-nuts, mhh...@gmail.com
so wise! thanks for those clarification.

I did not deeply understand why it is called an identity func, and why it has to be this signature.
It seems so narrowed to a specific case, makes me unhappy.

Now, i realized that even if <t>, for some specific cases, seems to solve some situations,
it won t be enough to implement the kind of things i presented,
function composition (do -> to composition).

My example was tooooo simple.

A better func to think about is mustNotErr.

mustNotErr is not implementable using <t>, but lets take a look what he d be,

func mustNotErr(f func() (<t>, error)) func() (<t>) {}

In plain text, it is a func that takes in parameter a func and returns a func.

but if you take some realistic example you ll get something like

func W() (X, error){}
func W1(a A, b *B) (Y, X, error){}

And now we can observe that size, positions, and types
of the delayed func IO/parameters are the differences
that are not matched in my previous declaration of mustNotErr.

mustNotErr takes no parameter, returns only 2 parameters
f() (A,B) -> matches W, but not W1.

But, intuitively we feel that W and W1 can be factorized to something like this,
a func                                      func(
with any input parameters           ...
,                                               ) (
returns any parameters,              ...
followed,                                    ,
by a traling error                          error
                                                )

yeah ?

Using that definition, let s see what it might look likes,

mustNotErr := func(f <t:func(...)(..., error)>) ??? {
 return func(params ...Parameters) ??? {
    ...ret, err := f(params...)
    if err != nil {
        panic(err)
    }
    return ret...
 }
}

Where Parameters{Value,Type}
Where "...,error" ~~ any front parameters until error
Where "...ret, err :=" ~~ any front parameters until error
Where "return ret..." ~~ any values in []Parameters

The problem now is about the type of the returned func,
it is not <t> anymore, because the error return parameter was removed,
on the other hand, as a declarer we dont know enough about f, to return a complete function,
it literally is func(...)(...)

But func(...)(...) is not a type a receiver can consume....

At that point, some sort of templating is required, indeed,
or what, partial types ? looks bad...

mhh...@gmail.com

unread,
Jul 21, 2017, 7:03:54 AM7/21/17
to golang-nuts, mhh...@gmail.com
here is a solution to what i described in previous.

The result of

func mustNotErr(f func() (<t>, error)) func() (<t>) {}
is the transitivity of t -to> func (...) (..., -error)

mustNotErr takes in input
a func T with any inputs, and a traling error,
returns in output,
the func T less the trailing error.

- Given

func W() (X, error){}
func W1(a A, b *B) (Y, X, error){}
- Want
func MustW() (X){}
func MustW1(a A, b *B) (Y, X){}

Which literally is,
W() (X, error) -> less a trailing error ->MustW() (X)
W1(a A, b *B) (Y, X, error) -> less a trailing error ->MustW1(a A, b *B) (Y, X)

Which in can be written such as
W() (X, error) -> func (...) (..., -error) ->MustW() (X)
W1(a A, b *B) (Y, X, error) -> func (...) (..., -error) ->MustW1(a A, b *B) (Y, X)

So the solution might looks like

mustNotErr := func(f <t:func(...)(..., error)>) t->func (...) (..., -error) {
 return func(params ...Parameters) ... {
Reply all
Reply to author
Forward
0 new messages