├── component ├── compose │ ├── main.go │ └── design.go └── component.go ├── union ├── union.go ├── example │ └── main.go └── union2.go ├── examples ├── counter │ ├── main.go │ └── counter │ │ └── counter.go └── counters │ ├── main.go │ └── counters │ └── counters.go ├── README.md ├── docs └── gox.md └── tea.go /component/compose/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | . "github.com/AlexanderChen1989/tea/component" 7 | ) 8 | 9 | func main() { 10 | fmt.Println("Hello") 11 | fmt.Printf("%+v\n", Div(nil, Div(nil, Div(nil)))) 12 | } 13 | -------------------------------------------------------------------------------- /union/union.go: -------------------------------------------------------------------------------- 1 | package union 2 | 3 | type Msg interface { 4 | msgType() 5 | } 6 | 7 | type MsgNone struct{} 8 | 9 | func (msg MsgNone) msgType() {} 10 | 11 | type MsgIncr int 12 | 13 | func (msg MsgIncr) msgType() {} 14 | 15 | type MsgDecr int 16 | 17 | func (msg MsgDecr) msgType() {} 18 | -------------------------------------------------------------------------------- /union/example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlexanderChen1989/tea/union" 7 | ) 8 | 9 | func main() { 10 | var msg union.Msg = union.MsgIncr(10) 11 | 12 | switch msg := msg.(type) { 13 | case union.MsgNone: 14 | case union.MsgIncr: 15 | fmt.Println("Incr", msg) 16 | case union.MsgDecr: 17 | fmt.Println("Decr", msg) 18 | default: 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /union/union2.go: -------------------------------------------------------------------------------- 1 | package union 2 | 3 | type Model int 4 | 5 | type None struct{} 6 | type Incr int 7 | type Decr int 8 | 9 | type CMsg interface { 10 | Process(Model) Model 11 | } 12 | 13 | func (msg None) Process(model Model) Model { 14 | return model 15 | } 16 | 17 | func (msg Incr) Process(model Model) Model { 18 | return Model(int(model) + int(msg)) 19 | } 20 | 21 | func (msg Decr) Process(model Model) Model { 22 | return Model(int(model) - int(msg)) 23 | } 24 | -------------------------------------------------------------------------------- /examples/counter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/AlexanderChen1989/tea" 7 | . "github.com/AlexanderChen1989/tea/examples/counter/counter" 8 | ) 9 | 10 | func main() { 11 | program, err := tea.NewProgram(Init, Update, View) 12 | if err != nil { 13 | panic(err) 14 | } 15 | defer program.Stop() 16 | 17 | ch := program.Start() 18 | 19 | ch <- Incr() 20 | ch <- Incr() 21 | ch <- Incr() 22 | ch <- Decr() 23 | ch <- Decr() 24 | ch <- Decr() 25 | 26 | time.Sleep(100 * time.Second) 27 | } 28 | -------------------------------------------------------------------------------- /component/compose/design.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | 5 | func User(name string, age int, num int = 10, children ...Node) Node { 6 | var pets []Node 7 | 8 | for i := 0; i < num; i++ { 9 | pets = append(pets,
Pet {i}
) 10 | } 11 | 12 | return ( 13 |
14 |
15 | {children} 16 |
17 |
18 | Pets: {pets} 19 |
20 |
21 | ) 22 | } 23 | 24 | func App() Node { 25 | return ( 26 | 27 |
Hello
28 |
29 | ) 30 | } 31 | 32 | 33 | */ 34 | -------------------------------------------------------------------------------- /component/component.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | type Props map[string]interface{} 4 | 5 | type Node struct { 6 | Name string 7 | Props Props 8 | Children []Node 9 | } 10 | 11 | func New(name string, props Props, children ...Node) Node { 12 | return Node{Name: name, Props: props, Children: children} 13 | } 14 | 15 | func Div(props Props, children ...Node) Node { 16 | return New("Div", props, children...) 17 | } 18 | 19 | func Network(props Props, children ...Node) Node { 20 | return New("Network", props, children...) 21 | } 22 | 23 | func Container(props Props, children ...Node) Node { 24 | return New("Container", props, children...) 25 | } 26 | -------------------------------------------------------------------------------- /examples/counters/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/AlexanderChen1989/tea" 7 | c "github.com/AlexanderChen1989/tea/examples/counter/counter" 8 | cs "github.com/AlexanderChen1989/tea/examples/counters/counters" 9 | ) 10 | 11 | func main() { 12 | program, err := tea.NewProgram(cs.Init, cs.Update, cs.View) 13 | if err != nil { 14 | panic(err) 15 | } 16 | defer program.Stop() 17 | 18 | ch := program.Start() 19 | 20 | ch <- cs.CounterA(c.Incr()) 21 | ch <- cs.CounterA(c.Incr()) 22 | ch <- cs.CounterB(c.Incr()) 23 | ch <- cs.CounterB(c.Incr()) 24 | ch <- cs.CounterA(c.Decr()) 25 | ch <- cs.CounterA(c.Decr()) 26 | 27 | time.Sleep(100 * time.Second) 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Elm Architecture In Go 2 | 3 | ## The Elm Version 4 | 5 | ```elm 6 | program 7 | : { init : (model, Cmd msg) 8 | , update : msg -> model -> (model, Cmd msg) 9 | , subscriptions : model -> Sub msg 10 | , view : model -> Html msg 11 | } -> Program Never 12 | ``` 13 | 14 | ## The Go Version without subscriptions 15 | ```go 16 | 17 | type Msg interface{} 18 | type Model interface{} 19 | type Cmd []func() Msg 20 | 21 | type Init func() (Model, Cmd) 22 | type Update func(Msg, Model) (Model, Cmd) 23 | type View func(Model) string 24 | 25 | type Program struct { 26 | init Init 27 | update Update 28 | view View 29 | 30 | context context.Context 31 | cancel func() 32 | } 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/gox.md: -------------------------------------------------------------------------------- 1 | ```gox 2 | type Msg enum { 3 | None, 4 | Incr(int, string), 5 | Decr(int), 6 | } 7 | 8 | msg := Msg.None 9 | msg := Msg.Incr(10, 20) 10 | msg := Msg.None 11 | 12 | match Msg { 13 | case None: 14 | case Incr(v1, v2): 15 | case Decr(v): 16 | default: 17 | } 18 | ``` 19 | 20 | ```go 21 | type { 22 | MsgNone struct {} 23 | 24 | MsgIncr struct { 25 | V1 int 26 | V2 string 27 | } 28 | 29 | MsgDecr struct { 30 | V1 int 31 | } 32 | } 33 | 34 | var msg interface{} = MsgNone{} 35 | var msg interface{} = MsgNone{V1: 10, V2: 20} 36 | 37 | switch msg := Msg.(type); msg { 38 | case MsgNone: 39 | case MsgIncr: 40 | v1, v2 := msg.V1, msg.V2 41 | case MsgDecr: 42 | v := msg.V1 43 | default: 44 | } 45 | 46 | ``` 47 | -------------------------------------------------------------------------------- /examples/counters/counters/counters.go: -------------------------------------------------------------------------------- 1 | package counters 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlexanderChen1989/tea" 7 | "github.com/AlexanderChen1989/tea/examples/counter/counter" 8 | ) 9 | 10 | type ( 11 | Model struct { 12 | CounterA tea.Model 13 | CounterB tea.Model 14 | } 15 | 16 | CounterAMsg tea.Msg 17 | CounterBMsg tea.Msg 18 | ) 19 | 20 | func CounterA(msg tea.Msg) tea.Msg { 21 | return CounterAMsg(msg) 22 | } 23 | 24 | func CounterB(msg tea.Msg) tea.Msg { 25 | return CounterBMsg(msg) 26 | } 27 | 28 | func Init() (tea.Model, tea.Cmd) { 29 | modelA, cmdA := counter.Init() 30 | modelB, cmdB := counter.Init() 31 | 32 | model := Model{modelA, modelB} 33 | cmd := append( 34 | tea.Tag(cmdA, CounterA), 35 | tea.Tag(cmdB, CounterB)..., 36 | ) 37 | return model, cmd 38 | } 39 | 40 | func Update(msg tea.Msg, model tea.Model) (tea.Model, tea.Cmd) { 41 | m, cmd := model.(Model), tea.Cmd{} 42 | 43 | switch msg.(type) { 44 | case CounterAMsg: 45 | m.CounterA, cmd = counter.Update(msg, m.CounterA) 46 | case CounterBMsg: 47 | m.CounterB, cmd = counter.Update(msg, m.CounterB) 48 | default: 49 | } 50 | 51 | return m, cmd 52 | } 53 | 54 | func View(model tea.Model) string { 55 | m := model.(Model) 56 | return fmt.Sprintf( 57 | "A => %s\nB => %s\n", 58 | counter.View(m.CounterA), 59 | counter.View(m.CounterB), 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /examples/counter/counter/counter.go: -------------------------------------------------------------------------------- 1 | package counter 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlexanderChen1989/tea" 7 | ) 8 | 9 | /* 10 | type Msg enum{ 11 | None, 12 | Incr(int, string), 13 | Decr(int), 14 | } 15 | 16 | msg := Msg.None 17 | msg := Msg.Incr(10, 20) 18 | msg := Msg.None 19 | 20 | match Msg { 21 | case None: 22 | case Incr(v1, v2): 23 | case Decr(v): 24 | default: 25 | } 26 | 27 | 28 | type { 29 | MsgNone struct {} 30 | 31 | MsgIncr struct { 32 | V1 int 33 | V2 string 34 | } 35 | 36 | Decr struct { 37 | V1 int 38 | } 39 | } 40 | 41 | var msg interface{} = MsgNone{} 42 | var msg interface{} = MsgNone{V1: 10, V2: 20} 43 | 44 | switch msg := Msg.(type); msg { 45 | case None: 46 | case Incr: 47 | v1, v2 := msg.V1, msg.V2 48 | case Decr: 49 | v := msg.V1 50 | default: 51 | } 52 | 53 | */ 54 | 55 | type ( 56 | Model int 57 | 58 | IncrMsg struct{} 59 | DecrMsg struct{} 60 | ) 61 | 62 | func Incr() tea.Msg { 63 | return IncrMsg{} 64 | } 65 | 66 | func Decr() tea.Msg { 67 | return DecrMsg{} 68 | } 69 | 70 | func Init() (tea.Model, tea.Cmd) { 71 | return Model(10), tea.Cmd{} 72 | } 73 | 74 | func Update(msg tea.Msg, model tea.Model) (tea.Model, tea.Cmd) { 75 | m := model.(Model) 76 | switch msg.(type) { 77 | case IncrMsg: 78 | return m + 1, tea.Cmd{} 79 | case DecrMsg: 80 | return m - 1, tea.Cmd{} 81 | default: 82 | return m, tea.Cmd{} 83 | } 84 | } 85 | 86 | func View(model tea.Model) string { 87 | return fmt.Sprint("Counter: ", model) 88 | } 89 | -------------------------------------------------------------------------------- /tea.go: -------------------------------------------------------------------------------- 1 | package tea 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | ) 9 | 10 | type Msg interface{} 11 | type Model interface{} 12 | 13 | type Cmd []func() Msg 14 | 15 | func Tag(cmd Cmd, tag func(Msg) Msg) Cmd { 16 | for i, fn := range cmd { 17 | if fn == nil { 18 | continue 19 | } 20 | cmd[i] = func() Msg { 21 | return tag(fn()) 22 | } 23 | } 24 | return cmd 25 | } 26 | 27 | type Init func() (Model, Cmd) 28 | type Update func(Msg, Model) (Model, Cmd) 29 | type View func(Model) string 30 | 31 | type Program struct { 32 | init Init 33 | update Update 34 | view View 35 | 36 | context context.Context 37 | cancel func() 38 | } 39 | 40 | func NewProgram(init Init, update Update, view View) (*Program, error) { 41 | if init == nil || update == nil || view == nil { 42 | return nil, errors.New("init or update or view cannot be nill") 43 | } 44 | ctx, cancel := context.WithCancel(context.Background()) 45 | program := &Program{ 46 | init: init, 47 | update: update, 48 | view: view, 49 | context: ctx, 50 | cancel: cancel, 51 | } 52 | return program, nil 53 | } 54 | 55 | func execCmd(ch chan Msg, cmd Cmd) { 56 | for _, fn := range cmd { 57 | if fn != nil { 58 | ch <- fn() 59 | } 60 | } 61 | } 62 | 63 | func (program *Program) Start() chan Msg { 64 | ch := make(chan Msg) 65 | go func() { 66 | model, cmd := program.init() 67 | fmt.Println(program.view(model)) 68 | go execCmd(ch, cmd) 69 | 70 | for { 71 | select { 72 | case msg := <-ch: 73 | newModel, cmd := program.update(msg, model) 74 | if !reflect.DeepEqual(newModel, model) { 75 | model = newModel 76 | fmt.Println(program.view(model)) 77 | } 78 | go execCmd(ch, cmd) 79 | case <-program.context.Done(): 80 | return 81 | } 82 | } 83 | }() 84 | return ch 85 | } 86 | 87 | func (program *Program) Stop() { 88 | program.cancel() 89 | } 90 | --------------------------------------------------------------------------------