Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: Go 2: permit directional variants of named channel types #21953

Open
robpike opened this issue Sep 20, 2017 · 13 comments

Comments

Projects
None yet
9 participants
@robpike
Copy link
Contributor

commented Sep 20, 2017

Consider this program:

package main

import (
	"fmt"
)

type Foo chan string

func main() {
	c := make(Foo, 1)
	c <- "gotcha"
	fmt.Println(<-c)
}

All is well. Now try to declare a direction for a Foo:

package main

type Foo chan string

func main() {
	var x <-Foo
}

// or

func x(c <-Foo) { }

This doesn't parse, but it's reasonable to expect it would and maybe, perhaps with parenthesization, easy to define without ambiguity. (A directional Foo with element type Foo could be tricky.)

Reported on twitter.

@robpike robpike changed the title spec: problem parsing named channels spec: problem parsing named directional channels Sep 20, 2017

@cespare

This comment has been minimized.

Copy link
Contributor

commented Sep 20, 2017

Isn't this kind of like doing

type MyInt int
var x uMyInt // Is this a uint?

i.e., I wouldn't expect this to work at all.

@dsnet

This comment has been minimized.

Copy link
Member

commented Sep 20, 2017

I'm tentatively marking this as a language change, unless you are suggesting that the current parser is not matching the written spec (in which case this is a bug).

@robpike

This comment has been minimized.

Copy link
Contributor Author

commented Sep 20, 2017

I think it's a language change.

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Sep 20, 2017

Definitively a language change, possibly backward compatible.

But why would we want to allow such a thing? It makes the direction arrow an operator on types, and only on channel types. And it only can make a bidirectional channel a read-only channel. What if I wanted to create a write-only channel? In the current design of the language <- arrow is really part of the channel syntax pattern, not a separate operator when it comes to channel types. The syntax is simply not compositional (we cannot interject a <- into the middle of a channel type).

I don't see why this would be worth the effort.

@leonklingele

This comment has been minimized.

Copy link
Contributor

commented Oct 9, 2017

This is requesting similar syntax to #22189.

@robpike

This comment has been minimized.

Copy link
Contributor Author

commented Oct 10, 2017

I'm not saying it is or is not worth the effort, but it definitely a flaw in the design that one can declare read-only channel types only if they are unnamed. I can't think of another property of the language that works like this, for instance that changes the possible types acceptable by a function.

@ianlancetaylor ianlancetaylor changed the title spec: problem parsing named directional channels proposal: Go 2: permit directional variants of named channel types Mar 20, 2018

@gopherbot gopherbot added this to the Proposal milestone Mar 20, 2018

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

commented Mar 20, 2018

See also #23415.

@mccolljr

This comment has been minimized.

Copy link

commented May 30, 2018

Take a look at this playground example: https://play.golang.org/p/ih1Uxm4Gfna

package main

import (
	"fmt"
)

type (
	Foo         chan string
	FooReadable <-chan string
	FooWritable chan<- string
)

func Send(f FooWritable, msg string) {
	f <- msg
}

func Recv(f FooReadable) string {
	return <-f
}

func Send2(f chan<- string, msg string) {
	f <- msg
}

func Recv2(f <-chan string) string {
	return <-f
}

func main() {
	Scenario1()
	Scenario2()
	Scenario3()
	Scenario4()
	Scenario5()
}

func Scenario1() {
	// works
	//
	foo := make(chan string)

	// compile error
	//
	// foo := make(Foo)

	go Send(foo, "message")
	fmt.Println(Recv(foo))
}

func Scenario2() {
	// works
	//
	// foo := make(chan string)

	// works as well
	//
	foo := make(Foo)

	go Send2(foo, "message2")
	fmt.Println(Recv2(foo))
}

func Scenario3() {
	foo := make(chan string)

	reader := FooReadable(foo)
	writer := FooWritable(foo)

	go Send(writer, "message3")
	fmt.Println(Recv(reader))

	go Send2(writer, "message4")
	fmt.Println(Recv2(reader))
}

func Scenario4() {
	foo := make(Foo)

	reader := (<-chan string)(foo)
	writer := (chan<- string)(foo)

	go Send(writer, "message5")
	fmt.Println(Recv(reader))

	go Send2(writer, "message6")
	fmt.Println(Recv2(reader))
}

func Scenario5() {
	temp := make(chan string)
	foo := Foo(temp)

	reader := (<-chan string)(foo)
	writer := (chan<- string)(foo)

	go Send(writer, "message7")
	fmt.Println(Recv(reader))

	go Send2(writer, "message8")
	fmt.Println(Recv2(reader))
}

Is this expected behavior? It seems odd to me that type conversion can be done in one direction but not the other:

  1. Foo can become chan string, <-chan string and chan<- string
  2. chan string can become Foo, FooReadable and FooWritable
  3. <-chan string can become FooReadable
  4. chan<- string can become FooWritable
  5. Foo cannot become FooReadable or FooWritable

Is the inability of Foo to be converted in this way intentional?

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

commented May 31, 2018

@mccolljr I don't think this proposal is the right place to discuss the details of Go type system. For the reasons for what you are seeing, see https://golang.org/ref/spec#Assignability. If you want to discuss this, see https://golang.org/wiki/Questions. If you want to propose a change, please use a different issue. Thanks.

@mccolljr

This comment has been minimized.

Copy link

commented May 31, 2018

@ianlancetaylor i apologize, I guess I was unclear. Please don't talk down to me, though.

I understand assignability and why you can't assign a named type to another named type without explicit conversion. I'm more concerned with why

https://play.golang.org/p/hqpgFfI26uG

...is legal, but explicitly converting a named channel type to a named directional channel type with the same underlying channel type (for ex. chan string) is forbidden, while the conversions, forced or otherwise, between the underlying type (chan string) and a named directional type (FooReadable or FooWritable) is perfectly legal as well.

The assignability rules in the spec don't exaclty make it clear to me that this is correct, and so my above post was meant to lead into that discussion.

It's relevant here because I was originally going to ask whether it would satisfy @davecheney to simply name the types for his receiving and sending directional channels similarly to his named bidirectional channel to offer the visual clarity. I was surprised that this particular conversion between channel types was impossible, given what else is legal in go.

I understand what has been said in this thread regarding why, as proposed, this is complex to add to the language lexically. I'm not sure I understand why this particular brand of conversion isn't allowed, though. I think that this conversion and the proposal are similar enough to warrant discussing this here.

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

commented May 31, 2018

@mccolljr I apologize for making you feel that I was talking down to you. That was in no way my intent.

That said, please keep this issue for discussion of the specific proposal above. Discussions of type assignability rules are simply not related to this proposal, which is not about assignability, or type conversions, at all. Thanks for your consideration.

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Jun 12, 2018

An possible problem with this proposal might be ambiguity of the parse tree (requiring type information to resolve it). To be investigated.

@pmaddams

This comment has been minimized.

Copy link

commented Jul 22, 2018

Hi folks. I think this proposal is a great idea; I came up with the following use case: https://play.golang.org/p/ktT0Mhgw8z8

// Public domain.

package main

import (
	"fmt"
	"math/big"
)

var (
	one = big.NewInt(1)
	ten = big.NewInt(10)
)

// stream represents a stream of arbitrary-precision integers.
type stream chan *big.Int

// values returns a stream of values in [lo, hi]. If hi is nil, the stream is infinite.
func values(lo, hi *big.Int) stream {
	st := make(stream)
	go func() {
		z := new(big.Int).Set(lo)
		for {
			if hi != nil && z.Cmp(hi) > 0 {
				break
			}
			st <- new(big.Int).Set(z)
			z.Add(z, one)
		}
		close(st)
	}()
	return st
}

func main() {
	for z := range values(one, ten) {
		fmt.Println(z)
	}
}

It would be nice to impose the additional constraint that the channel returned by “values” is receive-only. As is, because I want type safety, I’ve given up on the “stream” abstraction and have a family of functions that return <-chan *big.Int.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.