Repository: charmbracelet/bubbletea
Pull request #114 (2021-07-08 00:37:45)

made "batchMsg" public since it should be
because it carry's no special meaning which should be private,
and there are use cases where its necessary to access the Batched
Messages. (I know because i have one ;)

meowgorithm: Hey, Treilik! What’s your use case? Asking because `quitMsg` is similarly not exposed and I haven’t encountered a reason to expose either that nor `batchMsg` yet.
treilik: Hi meowgorithm!
in my [boxer](https://github.com/treilik/bubblesgum/#boxer----compose-bubbles-into-boxes-)-project ich have the case that i compose a recursive tree of tea.model's which call each subsequently the Update-method of the there children.
Because they can have more children, the returned cmd's are Batched and returned.
Since i use the cmd's as events (yet), i want to be able to check the type and value of the returned (batched) cmd's.
For me, there is a way around this.
But since i thought that it would be anyway more idiomatic,
to have a public method/function return something public, so i opened this pr.

muesli: I think this is a reasonable request, as a `BatchMsg` is merely a type alias for a slice of `Cmd`s, and once you `Batch` them up, you have no other way of accessing them.

I guess the danger is, that people could start using it in ways that make it a bit of a misnomer: when they actually just want a slice of `Cmd`s, not necessarily in the form of a message. Just to make sure we understand the request correctly, could you point us to a place in `boxer` where you would make use of that newly introduced public type?
treilik: No, not yet, but i can explain the case:
So i want to provide a way to let the models of the different boxes communicate with each other.
Since it is a recursive architecture the boxes do not know about each other.
So at compile time they have a address, and other **boxes** can an send a `cmd` which `msg` will be routed to the box having the address.
Since the content of the sending box can return a `batchMsg`, which will be embedded into the message send to the address, the receiving box receives a (private) `batchMsg` and can't acces the content.
content (`c1`) from leave box (`l1`) returns a `batchMsg`:

```
bubbletea runtime
        |
        n
       / \
     l1   l2
  ↑  /     \
 bM c1      c2
```

leave box (`l1`) embeddes returned `cmd` into a `addressCmd`

```
bubbletea runtime
   ↑    |
   ↑    n
   ↑   / \
  aC l1   l2
     /     \
    c1      c2
```

bubbletea calls the `addressCmd` and returns the `addressMsg` in which the `batchMsg` recides
and calls the update of the `rootNode` (`n`) with it.

```
bubbletea runtime   aM
        |            ↓
        n
       / \
     l1   l2
     /     \
    c1      c2
```

the `rootNode` (`n`) decides to which child it should send the `addressMsg` based on the address.
And sends it to (`l2`)

```
bubbletea runtime
        |
        n           aM
       / \           ↓
     l1   l2
     /     \
    c1      c2
```

`l2` recieves the `addressMsg` and since it is addressed to it, it takes the payload (a `batchMsg` after calling the `cmd`) and tries to act upon it.
But since its private it can't.

```
bubbletea runtime
        |
        n
       / \
     l1   l2     bM
     /     \
    c1      c2
```

As i sad i have a way around it, and i know that since i bypass a `cmd` around the bubbletea runtime, i currently misuse the `cmd` and bubbletea but that is/was my use-case.

meowgorithm: Thanks for the very detailed description, Treilik. One thing I'm not following is how you’re accessing `batchMsg`s.

`batchMsg` is produced by `BatchCmd` when it is called in the runtime ([here](https://github.com/charmbracelet/bubbletea/blob/v0.14.1/tea.go#L533)), however it's consumed a few lines down in the main loop ([here](https://github.com/charmbracelet/bubbletea/blob/v0.14.1/tea.go#L554)) before it ever reaches a Bubble Tea program’s main `Update`.

Let me know: I may be missing something.
treilik: I think this is the crucial part you are missing:

> Since the content of the sending box can return a batchMsg, **_which will be embedded into the message_** send to the address, the receiving box receives a (private) batchMsg and can't acces the content.

or to put it into other words:
The `Update` is not called (directly) from the Bubble Tea runtime, but from one of my `Update` from boxer. (That's way i drew the tree diagrams)
and thus i can embed the returned `cmd` into a new message which will bypass the bubbletea runtime and thus i have the problem of the private `batchMsg`.

This is my use-case, but maybe the core problem is, that as soon as a other program (my boxer) calls by it self a `Update` function of a 'tea.Model', and wants to act upon the 'cmd' this returns, a private return value is not helpful.

Hope this helps to clear up my problem, but aside from this, i do think that it would be nicer if public function return public types.

Thanks for taking the time :)
Cheers!

Patch:
diff --git a/commands.go b/commands.go
index 08f1a2e..10444e0 100644
--- a/commands.go
+++ b/commands.go
@@ -24,13 +24,13 @@ func Batch(cmds ...Cmd) Cmd {
 		return nil
 	}
 	return func() Msg {
-		return batchMsg(validCmds)
+		return BatchMsg(validCmds)
 	}
 }
 
-// batchMsg is the internal message used to perform a bunch of commands. You
-// can send a batchMsg with Batch.
-type batchMsg []Cmd
+// BatchMsg is a message used to perform a bunch of commands concurrently with
+// no ordering guarantees. You can send a BatchMsg with Batch.
+type BatchMsg []Cmd
 
 // Sequence runs the given commands one at a time, in order. Contrast this with
 // Batch, which runs commands concurrently.
diff --git a/tea.go b/tea.go
index f0e74c8..d2c19ff 100644
--- a/tea.go
+++ b/tea.go
@@ -295,7 +295,7 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
 				// NB: this blocks.
 				p.exec(msg.cmd, msg.fn)
 
-			case batchMsg:
+			case BatchMsg:
 				for _, cmd := range msg {
 					cmds <- cmd
 				}


Test patch:
diff --git a/commands_test.go b/commands_test.go
index 9caf18e..0ec1419 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -94,13 +94,13 @@ func TestBatch(t *testing.T) {
 	})
 	t.Run("single cmd", func(t *testing.T) {
 		b := Batch(Quit)()
-		if l := len(b.(batchMsg)); l != 1 {
+		if l := len(b.(BatchMsg)); l != 1 {
 			t.Fatalf("expected a []Cmd with len 1, got %d", l)
 		}
 	})
 	t.Run("mixed nil cmds", func(t *testing.T) {
 		b := Batch(nil, Quit, nil, Quit, nil, nil)()
-		if l := len(b.(batchMsg)); l != 2 {
+		if l := len(b.(BatchMsg)); l != 2 {
 			t.Fatalf("expected a []Cmd with len 2, got %d", l)
 		}
 	})
diff --git a/tea_test.go b/tea_test.go
index 4b087bd..6140677 100644
--- a/tea_test.go
+++ b/tea_test.go
@@ -108,7 +108,7 @@ func TestTeaBatchMsg(t *testing.T) {
 	m := &testModel{}
 	p := NewProgram(m, WithInput(&in), WithOutput(&buf))
 	go func() {
-		p.Send(batchMsg{inc, inc})
+		p.Send(BatchMsg{inc, inc})
 
 		for {
 			time.Sleep(time.Millisecond)