Skip to content

Commit

Permalink
no more dot tests
Browse files Browse the repository at this point in the history
  • Loading branch information
wcharczuk committed Feb 15, 2024
1 parent 123ee55 commit 2eec978
Show file tree
Hide file tree
Showing 5 changed files with 18 additions and 128 deletions.
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,9 @@ The effect of `Bind` is that "children" of a `Bind` node may have their heights

`Bind` nodes may also return `Bind` nodes themselves, creating fun and interesting implications for how an inner right-hand-side incremental needs to be propagated through multiple layers of binds, and reflect both its and the original bind children's true height through recomputations. To do this, we adopt the trick from the main ocaml library of creating two new pieces of state; a `bind-lhs-change` node that links the original bind input, and the right-hand-side (or output) incremental of the bind, making sure that the rhs respects the height of the transitive dependency of the bind's input. We also maintain a "scope", or a list of all the nodes that were created in respect to the rhs, and when the outer bind stabilizes, we also propagate changes to inner binds, regardless of where they are in the rhs graph. If this sounds complicated, it is!

An example of one such case:
# A word on `Scopes`

![Bind Regression](https://github.com/wcharczuk/go-incr/blob/main/_assets/bind_regression.png)

Because `Bind` nodes rely on scopes to operate correctly, the bind function you must provide takes a `Scope` argument. This scope argument should be passed to node constructors within the bind function. This lets us track which nodes were created in the bind scope, helping us maintain height invariants and link nodes correctly.
Because `Bind` nodes rely on scopes to operate correctly, and specifically know which nodes it was responsible for creating, the function you must provide to the `Bind` node constructor is passed `Scope` argument. This scope argument should be passed to node constructors within the bind function.

An example of a use case for bind might be:

Expand All @@ -99,7 +97,7 @@ t2 := incr.Bind(g, t2v, func(scope incr.Scope, t2vv string) Incr[string] {
})
```

Here `t1` is _not_ created within a bind scope, but the map that adds `" Ipsum"` to the value _is_ created within a bind scope. This is done transparently by passing the context through the `Map` constructor within the bind.
Here `t1` is _not_ created within a bind scope (it's created in the top level scope by passing in the `Graph` reference), but the map that adds `" Ipsum"` to the value _is_ created within a bind scope. This is done transparently by passing the scope through the `Map` constructor within the bind.

# Progress

Expand Down
Binary file removed _assets/bind_regression.png
Binary file not shown.
Binary file modified _assets/small_graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 15 additions & 3 deletions dot.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ func Dot(wr io.Writer, g *Graph) (err error) {
nodeLabels := make(map[Identifier]string)
for index, n := range nodes {
nodeLabel := fmt.Sprintf("n%d", index+1)
label := fmt.Sprintf(`label="%v" shape="rect"`, n)
nodeInternalLabel := fmt.Sprintf("%s\nlabel:%s\nheight: %d\nvalue: %v\n", n.Node().id.Short(), n.Node().label, n.Node().height, ExpertNode(n).Value())
label := fmt.Sprintf(`label = "%s" shape = "box3d"`, escapeForDot(nodeInternalLabel))
color := ` fillcolor = "white" style="filled" fontcolor="black"`
if n.Node().setAt > 0 {
if n.Node().setAt >= (g.stabilizationNum - 1) {
color = ` fillcolor = "red" style="filled" fontcolor="white"`
} else if n.Node().changedAt > 0 {
} else if n.Node().changedAt >= (g.stabilizationNum - 1) {
color = ` fillcolor = "pink" style="filled" fontcolor="black"`
}
writef(1, "node [%s%s]; %s", label, color, nodeLabel)
Expand All @@ -75,3 +76,14 @@ func Dot(wr io.Writer, g *Graph) (err error) {
writef(0, "}")
return
}

// escapeForDot escapes double quotes and backslashes, and replaces Graphviz's
// "center" character (\n) with a left-justified character.
// See https://graphviz.org/docs/attr-types/escString/ for more info.
func escapeForDot(str string) string {
return strings.ReplaceAll(
strings.ReplaceAll(
strings.ReplaceAll(str, `\`, `\\`),
`"`, `\"`),
`\l`, `\n`)
}
120 changes: 0 additions & 120 deletions dot_test.go
Original file line number Diff line number Diff line change
@@ -1,121 +1 @@
package incr

import (
"bytes"
"fmt"
"testing"

"github.com/wcharczuk/go-incr/testutil"
)

func Test_Dot(t *testing.T) {
golden := `digraph {
node [label="map2[563b29ae]@1" shape="rect" fillcolor = "white" style="filled" fontcolor="black"]; n1
node [label="var[7f8a4e21]@0" shape="rect" fillcolor = "white" style="filled" fontcolor="black"]; n2
node [label="var[884f9774]@0" shape="rect" fillcolor = "white" style="filled" fontcolor="black"]; n3
node [label="observer[033257be]" shape="rect" fillcolor = "white" style="filled" fontcolor="black"]; n4
n1 -> n4;
n2 -> n1;
n3 -> n1;
}
`

g := New()
v0 := Var(g, "foo")
ExpertNode(v0).SetID(MustParseIdentifier("165382c219e24e3db77fd41a884f9774"))
v1 := Var(g, "bar")
ExpertNode(v1).SetID(MustParseIdentifier("a985936bed8c48b99801a5bd7f8a4e21"))
m0 := Map2(g, v0, v1, concat)
ExpertNode(m0).SetID(MustParseIdentifier("fc45f4a7b5c7456f852f2298563b29ae"))
o := MustObserve(g, m0)
ExpertNode(o).SetID(MustParseIdentifier("507dd07419724979bb34f2ca033257be"))

buf := new(bytes.Buffer)
err := Dot(buf, g)
testutil.Nil(t, err)
testutil.Equal(t, golden, buf.String())
}

type errorWriter struct {
e error
}

func (ew errorWriter) Write(_ []byte) (int, error) {
return 0, ew.e
}

func Test_Dot_writeError(t *testing.T) {
g := New()
v0 := Var(g, "foo")
ExpertNode(v0).SetID(MustParseIdentifier("165382c219e24e3db77fd41a884f9774"))
v1 := Var(g, "bar")
ExpertNode(v1).SetID(MustParseIdentifier("a985936bed8c48b99801a5bd7f8a4e21"))
m0 := Map2(g, v0, v1, concat)
ExpertNode(m0).SetID(MustParseIdentifier("fc45f4a7b5c7456f852f2298563b29ae"))
o := MustObserve(g, m0)
ExpertNode(o).SetID(MustParseIdentifier("507dd07419724979bb34f2ca033257be"))

_ = MustObserve(g, m0)

buf := errorWriter{fmt.Errorf("this is just a test")}
err := Dot(buf, g)
testutil.NotNil(t, err)
}

func Test_Dot_setAt(t *testing.T) {
golden := `digraph {
node [label="map2[563b29ae]@1" shape="rect" fillcolor = "white" style="filled" fontcolor="black"]; n1
node [label="var[7f8a4e21]@0" shape="rect" fillcolor = "red" style="filled" fontcolor="white"]; n2
node [label="var[884f9774]@0" shape="rect" fillcolor = "white" style="filled" fontcolor="black"]; n3
node [label="observer[033257be]" shape="rect" fillcolor = "white" style="filled" fontcolor="black"]; n4
n1 -> n4;
n2 -> n1;
n3 -> n1;
}
`

g := New()
v0 := Var(g, "foo")
ExpertNode(v0).SetID(MustParseIdentifier("165382c219e24e3db77fd41a884f9774"))
v1 := Var(g, "bar")
ExpertNode(v1).SetID(MustParseIdentifier("a985936bed8c48b99801a5bd7f8a4e21"))
v1.Node().setAt = 1
m0 := Map2(g, v0, v1, concat)
ExpertNode(m0).SetID(MustParseIdentifier("fc45f4a7b5c7456f852f2298563b29ae"))
o := MustObserve(g, m0)
ExpertNode(o).SetID(MustParseIdentifier("507dd07419724979bb34f2ca033257be"))

buf := new(bytes.Buffer)
err := Dot(buf, g)
testutil.Nil(t, err)
testutil.Equal(t, golden, buf.String())
}

func Test_Dot_changedAt(t *testing.T) {
golden := `digraph {
node [label="map2[563b29ae]@1" shape="rect" fillcolor = "pink" style="filled" fontcolor="black"]; n1
node [label="var[7f8a4e21]@0" shape="rect" fillcolor = "white" style="filled" fontcolor="black"]; n2
node [label="var[884f9774]@0" shape="rect" fillcolor = "white" style="filled" fontcolor="black"]; n3
node [label="observer[033257be]" shape="rect" fillcolor = "white" style="filled" fontcolor="black"]; n4
n1 -> n4;
n2 -> n1;
n3 -> n1;
}
`

g := New()
v0 := Var(g, "foo")
ExpertNode(v0).SetID(MustParseIdentifier("165382c219e24e3db77fd41a884f9774"))
v1 := Var(g, "bar")
ExpertNode(v1).SetID(MustParseIdentifier("a985936bed8c48b99801a5bd7f8a4e21"))
m0 := Map2(g, v0, v1, concat)
ExpertNode(m0).SetID(MustParseIdentifier("fc45f4a7b5c7456f852f2298563b29ae"))
m0.Node().changedAt = 1
o := MustObserve(g, m0)
ExpertNode(o).SetID(MustParseIdentifier("507dd07419724979bb34f2ca033257be"))

buf := new(bytes.Buffer)
err := Dot(buf, g)
testutil.Nil(t, err)
testutil.Equal(t, golden, buf.String())
}

0 comments on commit 2eec978

Please sign in to comment.