├── 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 |
--------------------------------------------------------------------------------