type switch for interface

1,253 views
Skip to first unread message

i3dmaster

unread,
Aug 24, 2011, 1:09:54 PM8/24/11
to golang-nuts
I have a question for how type value is evaluated when a func is
taking an interface value. Here is a toy to illustrate my question.

type Foo interface
{
Bytes() []byte
}

type Bar interface {
Foo
String() string
}

type foo struct {
cache []byte
}

type bar struct {
cache []byte
}

// bar implements Foo interface
func (b bar) Bytes() []byte {
return b.cache
}

// foo implements Bar interface
func (f foo) Bytes() []byte {
return f.cache
}

func (f foo) String() string {
return string(f.cache)
}

func Workable(f Foo) string {
switch t := f.(type) {
case bar, *bar:
return string(t.Bytes())
case foo:
return t.String()
case *foo:
return t.String()
default:
fmt.Printf("unknown type %T\n", t)
}
return "unknown"
}

func DoesNotWork(f Foo) string {
switch t := f.(type) {
case bar, *bar:
return string(t.Bytes())
case foo, *foo:
return t.String()
default:
fmt.Printf("unknown type %T\n", t)
}
return "unknown"
}

If you notice that the _only_ difference b/w Workable and DoesNotWork
is I put foo, *foo into the same case evaluation clause, and that
throws

t.String undefined (type Foo has no field or method String)

String() should be available for either foo or *foo type, why the
second form does not work? Does case foo, bar: means if the value is
either foo or bar?

Ian Lance Taylor

unread,
Aug 24, 2011, 1:38:47 PM8/24/11
to i3dmaster, golang-nuts
i3dmaster <i3dm...@gmail.com> writes:

> case foo, *foo:
> return t.String()

> t.String undefined (type Foo has no field or method String)

When you use a single type in a case, a new variable with the same name
is introduced with that type. When you use multiple types in a case,
this does not happen, because it is not clear what the type should be.
So here t has type Foo, and you are asking for the String method of Foo,
but it doesn't have one.

Ian

Yongjian Xu

unread,
Aug 25, 2011, 3:17:11 AM8/25/11
to Ian Lance Taylor, golang-nuts
Hmm...Sorry I still don't get it. f has type Foo but with t := f.(type), t should have the actual type value (like type case in other lang. Am I understanding it wrong?) so the logic should really be "comparing t's type to each of the type(s) in cases and if it evaluate to true, then exec the statement. With "case foo, *foo", I would assume the compiler should generate the code to iterate the types provided and see if any one of them is the same as t. 

I think writing code like the Workable() case is quite awkward, I should be able to provide types that known to have the required methods with just one case clause instead of enumerating them individually. Is there a reason it is technically impossible to implement?
 

Ian

chris dollin

unread,
Aug 25, 2011, 3:39:31 AM8/25/11
to Yongjian Xu, Ian Lance Taylor, golang-nuts
On 25 August 2011 08:17, Yongjian Xu <i3dm...@gmail.com> wrote:
> On Wed, Aug 24, 2011 at 10:38 AM, Ian Lance Taylor <ia...@google.com> wrote:
>>
>> i3dmaster <i3dm...@gmail.com> writes:
>>
>> >     case foo, *foo:
>> >       return t.String()
>>
>> > t.String undefined (type Foo has no field or method String)
>>
>> When you use a single type in a case, a new variable with the same name
>> is introduced with that type.  When you use multiple types in a case,
>> this does not happen, because it is not clear what the type should be.
>> So here t has type Foo, and you are asking for the String method of Foo,
>> but it doesn't have one.
>
> Hmm...Sorry I still don't get it. f has type Foo but with t := f.(type), t
> should have the actual type value (like type case in other lang. Am I
> understanding it wrong?)

Yes.

As Ian said, with `case A, B`, the variable (`t` above) is bound to
the /general/ type of f, not any of the types A and B. (How would you
choose which of A and B to bind?)

> so the logic should really be "comparing t's type
> to each of the type(s) in cases and if it evaluate to true, then exec the
> statement.

f's type, not t's.

> With "case foo, *foo", I would assume the compiler should
> generate the code to iterate the types provided and see if any one of them
> is the same as t.

Yes. But the /static type/ of t is the same as that of f.

> I think writing code like the Workable() case is quite awkward, I should be
> able to provide types that known to have the required methods with just one
> case clause instead of enumerating them individually.

Define an interface with those methods and use it as the
case value.

> Is there a reason it is technically impossible to implement?

No. But mere technical non-impossibility isn't enough.

Chris

--
Chris "allusive" Dollin

Yongjian Xu

unread,
Aug 25, 2011, 4:51:00 AM8/25/11
to chris dollin, Ian Lance Taylor, golang-nuts
On Thu, Aug 25, 2011 at 12:39 AM, chris dollin <ehog....@googlemail.com> wrote:
On 25 August 2011 08:17, Yongjian Xu <i3dm...@gmail.com> wrote:
> On Wed, Aug 24, 2011 at 10:38 AM, Ian Lance Taylor <ia...@google.com> wrote:
>>
>> i3dmaster <i3dm...@gmail.com> writes:
>>
>> >     case foo, *foo:
>> >       return t.String()
>>
>> > t.String undefined (type Foo has no field or method String)
>>
>> When you use a single type in a case, a new variable with the same name
>> is introduced with that type.  When you use multiple types in a case,
>> this does not happen, because it is not clear what the type should be.
>> So here t has type Foo, and you are asking for the String method of Foo,
>> but it doesn't have one.
>
> Hmm...Sorry I still don't get it. f has type Foo but with t := f.(type), t
> should have the actual type value (like type case in other lang. Am I
> understanding it wrong?)

Yes.

As Ian said, with `case A, B`, the variable (`t` above) is bound to
the /general/ type of f, not any of the types A and B. (How would you
choose which of A and B to bind?)

Its not completely unreasonable to iterate the binding through the type list. I don't the reason why it has to choose _one_ to bind, but maybe its not worth to go with the extra miles...
 

> so the logic should really be "comparing t's type
> to each of the type(s) in cases and if it evaluate to true, then exec the
> statement.

f's type, not t's.

Well, should it be t's type (after binding to each literal type in cases) to match with the dynamic type f?
 

> With "case foo, *foo", I would assume the compiler should
> generate the code to iterate the types provided and see if any one of them
> is the same as t.

Yes. But the /static type/ of t is the same as that of f.

But with each case, t gets bound to those literal types... 
"The TypeSwitchGuard may include a short variable declaration. When that form is used, the variable is declared in each clause. In clauses with a case listing exactly one type, the variable has that type; otherwise, the variable has the type of the expression in the TypeSwitchGuard.
"
If I understand this statement correctly, t's actual type changes in each case. It is not always the same as f unless (under the current implementation) when there are multiple types cases. What I was questioning was why couldn't t be bound to each of the type literals in the multiple types case and just iterate through them...

 

> I think writing code like the Workable() case is quite awkward, I should be
> able to provide types that known to have the required methods with just one
> case clause instead of enumerating them individually.

Define an interface with those methods and use it as the
case value.

Ok good tips. Maybe this is the idiomatic way in Go to express this logic. So I could just 

case Bar:
   blah()
case Foo:
   blah()

and just switch on interfaces, not actual types.
 

> Is there a reason it is technically impossible to implement?

No. But mere technical non-impossibility isn't enough.

Ok, so what are the concerns? performance? expressiveness? readability?...

chris dollin

unread,
Aug 25, 2011, 5:23:09 AM8/25/11
to Yongjian Xu, Ian Lance Taylor, golang-nuts

`t` only gets to have one type, and the choices are the type of f,
the type A, or the type B. Whichever of A and B it picks, it's wrong
(in general) if f actually has the other one. So neither is the right
choice, so it must be the static type of f.

>> Yes. But the /static type/ of t is the same as that of f.
>
> But with each case, t gets bound to those literal types...

When there's only one type in the case, not if there's more.

> "The TypeSwitchGuard may include a short variable declaration. When that
> form is used, the variable is declared in each clause. In clauses with a
> case listing exactly one type, the variable has that type; otherwise, the
> variable has the type of the expression in the TypeSwitchGuard.
> "
> If I understand this statement correctly, t's actual type changes in each
> case.

Each case has a t with the appropriate type.


>> > I think writing code like the Workable() case is quite awkward, I should
>> > be
>> > able to provide types that known to have the required methods with just
>> > one
>> > case clause instead of enumerating them individually.
>>
>> Define an interface with those methods and use it as the
>> case value.
>
> Ok good tips. Maybe this is the idiomatic way in Go to express this logic.
> So I could just
> case Bar:
>    blah()
> case Foo:
>    blah()
> and just switch on interfaces, not actual types.

case MyInterfaceType:
t.TheAppropriateMethod()

You're not intereted in Bar vs Foo, but just the common interface
they have.

>> > Is there a reason it is technically impossible to implement?
>>
>> No. But mere technical non-impossibility isn't enough.
>
> Ok, so what are the concerns? performance? expressiveness? readability?...

Mere technical non-impossibility isn't enough to adopt some
feature F; there has to be enough value in doing so.
Performance,expressiveness, and readability are some of the
things that play into it. Go puts great value on simplicity. And
features compete for a limited resource -- developer time.

Reply all
Reply to author
Forward
0 new messages