Go API Examples Ported to Julia
Simon Frost
Introduction
The Go SSTorytime library includes four API examples demonstrating graph construction, hub joins, maze solving, and loop-corrected path finding. This vignette ports each example to Julia using SemanticSpacetime.jl’s MemoryStore, showing how the Go patterns translate idiomatically.
Setup
using SemanticSpacetime
SemanticSpacetime.reset_arrows!()
SemanticSpacetime.reset_contexts!()
# Register arrows used across examples
then_f = insert_arrow!("LEADSTO", "then", "leads to next", "+")
then_b = insert_arrow!("LEADSTO", "prev", "preceded by", "-")
insert_inverse_arrow!(then_f, then_b)
fwd_f = insert_arrow!("LEADSTO", "fwd", "forward link", "+")
fwd_b = insert_arrow!("LEADSTO", "bwd", "backward link", "-")
insert_inverse_arrow!(fwd_f, fwd_b)
has_f = insert_arrow!("CONTAINS", "has", "contains", "+")
has_b = insert_arrow!("CONTAINS", "in", "is in", "-")
insert_inverse_arrow!(has_f, has_b)
belongs_f = insert_arrow!("CONTAINS", "belongs to", "belongs to", "-")
belongs_b = insert_arrow!("CONTAINS", "owns", "owns", "+")
insert_inverse_arrow!(belongs_b, belongs_f)
println("Arrows registered.")Arrows registered.API Example 1: Mary’s Lamb Story
The Go API_EXAMPLE_1 creates a nursery-rhyme story graph with SST.Vertex() and SST.Edge(), then traces paths forward. Here we do the same with mem_vertex! and mem_edge!.
store1 = MemoryStore()
chap = "home and away"
n1 = mem_vertex!(store1, "Mary had a little lamb", chap)
n2 = mem_vertex!(store1, "Whose fleece was dull and grey", chap)
n3 = mem_vertex!(store1, "And every time she washed it clean", chap)
n4 = mem_vertex!(store1, "It just went to roll in the hay", chap)
n5 = mem_vertex!(store1, "And when it reached a certain age", chap)
n6 = mem_vertex!(store1, "She'd serve it on a tray", chap)
mem_edge!(store1, n1, "then", n2, String[], 1.0f0)
mem_edge!(store1, n2, "then", n3, String[], 0.5f0)
mem_edge!(store1, n2, "then", n5, String[], 0.5f0)
mem_edge!(store1, n3, "then", n4, String[], 1.0f0)
mem_edge!(store1, n5, "then", n6, String[], 1.0f0)
println("Story graph: $(node_count(store1)) nodes, $(link_count(store1)) links")Story graph: 6 nodes, 10 linksNow trace the branching narrative with forward_cone, mirroring the Go GetFwdPathsAsLinks call:
cone = forward_cone(store1, n1.nptr; depth=4)
println("Forward cone from 'Mary had a little lamb':")
println(" Paths: $(length(cone.paths))")
for nptr in cone.supernodes
node = mem_get_node(store1, nptr)
if node !== nothing
println(" → '$(node.s)'")
end
endForward cone from 'Mary had a little lamb':
Paths: 5
→ 'Whose fleece was dull and grey'
→ 'And every time she washed it clean'
→ 'And when it reached a certain age'The story branches at line 2 into two endings — exactly as the Go example shows:
result = find_paths(store1, n1.nptr, n4.nptr; max_depth=5)
println("Paths to 'roll in the hay': $(length(result.paths))")
for (i, path) in enumerate(result.paths)
names = [mem_get_node(store1, p).s for p in path if mem_get_node(store1, p) !== nothing]
println(" Path $i: ", join(names, " → "))
end
result2 = find_paths(store1, n1.nptr, n6.nptr; max_depth=5)
println("\nPaths to 'serve on a tray': $(length(result2.paths))")
for (i, path) in enumerate(result2.paths)
names = [mem_get_node(store1, p).s for p in path if mem_get_node(store1, p) !== nothing]
println(" Path $i: ", join(names, " → "))
endPaths to 'roll in the hay': 1
Path 1: Mary had a little lamb → Whose fleece was dull and grey → And every time she washed it clean → It just went to roll in the hay
Paths to 'serve on a tray': 1
Path 1: Mary had a little lamb → Whose fleece was dull and grey → And when it reached a certain age → She'd serve it on a trayAPI Example 2: HubJoin
The Go API_EXAMPLE_2 creates nodes and joins them to a central hub using SST.HubJoin(). In Julia, hub_join! requires an SSTConnection (database-backed). We replicate the logic using mem_vertex! and mem_edge! to create the same hub pattern in memory.
store2 = MemoryStore()
names = ["test_node1", "test_node2", "test_node3"]
weights = Float32[0.2, 0.4, 1.0]
context = ["some", "context", "tags"]
nodes = [mem_vertex!(store2, n, "my chapter") for n in names]
# Create an auto-named hub (like Go's HubJoin with empty name)
hub1 = mem_vertex!(store2, "hub_$(join(names, '_'))", "my chapter")
for (i, node) in enumerate(nodes)
mem_edge!(store2, node, "then", hub1, context, weights[i])
end
println("Hub 1: '$(hub1.s)' with $(length(nodes)) spokes")
# Create a named hub (like Go's HubJoin with "mummy_node")
hub2 = mem_vertex!(store2, "mummy_node", "my chapter")
for node in nodes
mem_edge!(store2, node, "belongs to", hub2)
end
println("Hub 2: '$(hub2.s)' with $(length(nodes)) members")
println("Store: $(node_count(store2)) nodes, $(link_count(store2)) links")Hub 1: 'hub_test_node1_test_node2_test_node3' with 3 spokes
Hub 2: 'mummy_node' with 3 members
Store: 5 nodes, 12 linksInspect the hub structure:
for node in nodes
total = sum(length(ch) for ch in node.incidence)
println("'$(node.s)': $total connections")
end'test_node1': 2 connections
'test_node2': 2 connections
'test_node3': 2 connectionsAPI Example 3: Maze Solver
The Go API_EXAMPLE_3 encodes 9 maze corridors and finds paths from maze_a7 to maze_i6. We build the same graph in memory and use find_paths:
store3 = MemoryStore()
maze_paths = [
["maze_a7","maze_b7","maze_b6","maze_c6","maze_c5","maze_b5","maze_b4","maze_a4","maze_a3","maze_b3","maze_c3","maze_d3","maze_d2","maze_e2","maze_e3","maze_f3","maze_f4","maze_e4","maze_e5","maze_f5","maze_f6","maze_g6","maze_g5","maze_g4","maze_h4","maze_h5","maze_h6","maze_i6"],
["maze_d1","maze_d2"],
["maze_f1","maze_f2","maze_e2"],
["maze_f2","maze_g2","maze_h2","maze_h3","maze_g3","maze_g2"],
["maze_b1","maze_c1","maze_c2","maze_b2","maze_b1"],
["maze_b7","maze_b8","maze_c8","maze_c7","maze_d7","maze_d6","maze_e6","maze_e7","maze_f7","maze_f8"],
["maze_d7","maze_d8","maze_e8","maze_e7"],
["maze_f7","maze_g7","maze_g8","maze_h8","maze_h7"],
["maze_a2","maze_a1"],
]
for corridor in maze_paths
for leg in 2:length(corridor)
nfrom = mem_vertex!(store3, corridor[leg-1], "solve maze")
nto = mem_vertex!(store3, corridor[leg], "solve maze")
mem_edge!(store3, nfrom, "fwd", nto)
end
end
println("Maze: $(node_count(store3)) nodes, $(link_count(store3)) links")Maze: 56 nodes, 112 linksSolve from start to end:
start_nodes = mem_get_nodes_by_name(store3, "maze_a7")
end_nodes = mem_get_nodes_by_name(store3, "maze_i6")
if !isempty(start_nodes) && !isempty(end_nodes)
solutions = find_paths(store3, start_nodes[1].nptr, end_nodes[1].nptr; max_depth=30)
println("Solutions found: $(length(solutions.paths)) DAG paths, $(length(solutions.loops)) loop paths")
for (i, path) in enumerate(solutions.paths)
names = [mem_get_node(store3, p).s for p in path if mem_get_node(store3, p) !== nothing]
println(" Story $i ($(length(names)) steps): $(names[1]) → ... → $(names[end])")
end
endSolutions found: 1 DAG paths, 0 loop paths
Story 1 (28 steps): maze_a7 → ... → maze_i6API Example 4: Loop-Corrected Paths (Double Slit)
The Go API_EXAMPLE_4 loads doubleslit.n4l and finds paths from A1 to B6 with loop corrections. We build the graph in memory from the N4L structure:
store4 = MemoryStore()
# Build the double-slit graph manually (matching doubleslit.n4l)
edges = [
("A1","A2"), ("A1","A3"), ("A2","A5"), ("A3","A5"),
("A3","A6"), ("A4","S1"), ("A5","S1"), ("A5","S2"),
("A6","S2"), ("S1","B1"), ("S2","B2"), ("S2","B3"),
("B1","B4"), ("B2","B4"), ("B2","B5"), ("B3","B5"),
("B4","B6"),
]
for (src, dst) in edges
nfrom = mem_vertex!(store4, src, "double slit example")
nto = mem_vertex!(store4, dst, "double slit example")
mem_edge!(store4, nfrom, "fwd", nto)
end
println("Double slit graph: $(node_count(store4)) nodes, $(link_count(store4)) links")Double slit graph: 14 nodes, 34 linksFind paths with loop detection — the Go version uses colliding wavefronts; we use find_paths which separates DAG paths from loop corrections:
a1_nodes = mem_get_nodes_by_name(store4, "A1")
b6_nodes = mem_get_nodes_by_name(store4, "B6")
if !isempty(a1_nodes) && !isempty(b6_nodes)
result = find_paths(store4, a1_nodes[1].nptr, b6_nodes[1].nptr; max_depth=10)
println("-- T R E E --")
println("DAG paths from A1 to B6: $(length(result.paths))")
for (i, path) in enumerate(result.paths)
names = [mem_get_node(store4, p).s for p in path if mem_get_node(store4, p) !== nothing]
println(" Story $i: ", join(names, " → "))
end
println("\n++ L O O P S ++")
println("Loop corrections: $(length(result.loops))")
for (i, path) in enumerate(result.loops)
names = [mem_get_node(store4, p).s for p in path if mem_get_node(store4, p) !== nothing]
println(" Loop $i: ", join(names, " → "))
end
end-- T R E E --
DAG paths from A1 to B6: 5
Story 1: A1 → A2 → A5 → S1 → B1 → B4 → B6
Story 2: A1 → A2 → A5 → S2 → B2 → B4 → B6
Story 3: A1 → A3 → A5 → S1 → B1 → B4 → B6
Story 4: A1 → A3 → A5 → S2 → B2 → B4 → B6
Story 5: A1 → A3 → A6 → S2 → B2 → B4 → B6
++ L O O P S ++
Loop corrections: 0Explore the cone structure to see how the wavefronts expand:
if !isempty(a1_nodes)
for d in [2, 3, 4, 5]
fwd_cone = forward_cone(store4, a1_nodes[1].nptr; depth=d)
println("Forward cone depth $d: $(length(fwd_cone.supernodes)) supernodes, $(length(fwd_cone.paths)) paths")
end
endForward cone depth 2: 3 supernodes, 5 paths
Forward cone depth 3: 6 supernodes, 10 paths
Forward cone depth 4: 9 supernodes, 18 paths
Forward cone depth 5: 11 supernodes, 29 pathsSummary
These four Go API examples translate naturally to Julia:
| Go API | Julia Equivalent |
|---|---|
SST.Vertex(sst, name, chap) | mem_vertex!(store, name, chap) |
SST.Edge(sst, from, arrow, to, ctx, w) | mem_edge!(store, from, arrow, to, ctx, w) |
SST.HubJoin(sst, name, chap, ptrs, arrow, ctx, w) | Manual hub pattern with mem_vertex! + mem_edge! |
SST.GetFwdPathsAsLinks(sst, ptr, st, depth, limit) | forward_cone(store, ptr; depth=d) |
SST.GetPathsAndSymmetries(sst, left, right, ...) | find_paths(store, from, to; max_depth=d) |
SST.WaveFrontsOverlap(sst, left, right, ...) | find_paths separates .paths from .loops |