Slide 1

Slide 1 text

ೖ໳ Bubble Tea 2022 - 0 6 - 1 1 motemen Kyoto.go LT

Slide 2

Slide 2 text

#ͱ͸

Slide 3

Slide 3 text

charmbracelet/ bubbletea Go TUI The fun, functional and stateful way to build terminal apps. A Go framework based on The Elm Architecture.

Slide 4

Slide 4 text

The Elm Architecture

Slide 5

Slide 5 text

Elm ݴޠ https://elm-lang.org/ Haskell JavaScript Redux Prior Art https://redux.js.org/understanding/history-and-design/prior- art#elm

Slide 6

Slide 6 text

The Elm Architecture • Model: ΞϓϦέʔγϣϯͷεςʔτ • View: Model ͔Β HTML Λੜ੒ • Update: Msg ʹج͖ͮ Model Λߋ৽ • Msg ͸ϢʔβೖྗͳͲϞσϧͷ֎෦ View() Update(msg) Model Msg

Slide 7

Slide 7 text

1 ࣮૷ͯ͠ΈΔ: counter

Slide 8

Slide 8 text

interface tea.Model // Model contains the program's state as well as its core functions. type Model interface { // Init is the first function that will be called. It returns an optional // initial command. To not perform an initial command return nil. Init() Cmd // Update is called when a message is received. Use it to inspect messages // and, in response, update the model and/or send a command. Update(Msg) (Model, Cmd) // View renders the program's UI, which is just a string. The view is // rendered after every Update. View() string }

Slide 9

Slide 9 text

model, View() string type model struct { count int } func (model) Init() tea.Cmd { return nil } func (m model) View() string { return fmt.Sprintf("count: %v", m.count) }

Slide 10

Slide 10 text

Update(tea.Msg) (tea.Model, tea.Cmd) func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { case " ": m.count = m.count + 1 return m, nil } } return m, nil } ߋ৽͞ΕͨϞσϧΛฦ͢ View() ͕ը໘ʹඳը͞ΕΔ

Slide 11

Slide 11 text

Msg ͸Ϣʔβʗ֎ք͔Βͷೖྗ Msg contain data from the result of a IO operation. Msgs trigger the update function and, henceforth, the UI. tea.KeyMsg tea.MouseMsg tea.WindowMsg Msg type Msg interface{}

Slide 12

Slide 12 text

bubbletea ʹ৐ͤΔ 🚀 func main() { prog := tea.NewProgram(model{count: 0}) err := prog.Start() if err != nil { log.Fatal(err) } }

Slide 13

Slide 13 text

ऴྃͰ͖ΔΑ͏ʹ͢Δ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { case "q", "ctrl+c": return m, tea.Quit case " ": m.count = m.count + 1 return m, nil } } return m, nil } CmdΛฦ͢

Slide 14

Slide 14 text

2 tea.Cmd Λ࢖ͬͯΈΔ

Slide 15

Slide 15 text

tea.Cmd • “Cmd is an IO operation that returns a message when it's complete” • ֎ք΁ͷ໋ྩ • ऴΘͬͨΒ Msg Λ໭ؔ͢਺ • goroutine Ͱ࣮ߦ͞ΕΔ View() Cmd Update(msg) Model Msg

Slide 16

Slide 16 text

HTTP ௨৴Λ൐͏ྫ type countLoadedMsg struct { count int } func (m model) hitCounter() tea.Msg { count, err := m.api.Hit() if err != nil { panic(err) } return countLoadedMsg{count: count} } var _ tea.Cmd = model{}.hitCounter ͜ͷϝιουͷܕ͕ tea.Cmd IO͕ऴΘͬͨΒMsgΛฦ͢

Slide 17

Slide 17 text

ΦϦδφϧͷ Msg Λड͚औΔ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { case " ": m.loading = true return m, m.hitCounter } case countLoadedMsg: m.count = msg.count m.loading = false return m, nil } return m, nil } CmdΛฦ͢ MsgΛड͚औͬͨΒϞσϧߋ৽

Slide 18

Slide 18 text

Init(), View() func (m model) Init() tea.Cmd { return m.hitCounter } func (m model) View() string { if m.loading { return "..." } else { return fmt.Sprintf("count: %v", m.count) } } ϓϩάϥϜ։࢝࣌ʹൃߦ͍ͨ͠Cmd

Slide 19

Slide 19 text

3 bubbles ΋࢖ͬͯΈΔ

Slide 20

Slide 20 text

bubbles ࢖ͬͨྫ type model struct { ... spinner spinner.Model } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd m.spinner, cmd = m.spinner.Update(msg) ... } func (m model) View() string { if m.loading { return m.spinner.View() } ... } func (m model) Init() tea.Cmd { return tea.Batch( m.hitCounter, m.spinner.Tick, ) } • charmbracelet/bubbles • bubbletea ༻ͷ TUI ίϯ ϙʔωϯτू ModelΛೖΕࢠʹ ࢠڙʹ΋MsgΛ౉͢ Ξχϝʔγϣϯ༻Cmd

Slide 21

Slide 21 text

github.com/motemen/example-go-bubbletea The Go gopher is designed by Renee French, licensed under CC BY 3.0