4 ms·
Looking at the syntax, I wonder how close one could get to it in a "normal" programming language. x -> y: hello world declares a connection between tw
by mg 1y ago
Looking at the syntax, I wonder how close one could get to it in a "normal" programming language.
x -> y: hello world
declares a connection between two
shapes, x and y, with the label,
hello world
When I read this, I see this Python line in my mind:
connect('x', 'y', 'hello world')
This is of course a lot longer. The reason is that D2 seems to rather use operators than functions. So another approach could be
'x' * 'y' + 'hello world'
This would be possible to implement if the language supports overloading the __add__ and __mul__ functions of the str class. Python does not support it though. So I guess one would have to put at least one instance of a custom class into the mix. Say 'scene', then one might be able to achieve the above with this line:
scene + 'x' * 'y' + 'hello world'
Meaning "Put a connection between x and y with label 'hello world' into the scene".
Hmm.. also not very close to the D2 syntax. So a DSL for diagrams seems to be warranted.
- sjrd 1y agoIn Scala you can do it, because you can define your own operators (which are nothing but method names), and you can extend types you don't control. You are a bit constrained by the operator precedence rules, but it's usually good enough. It's bad practice to make DSLs left and right, obviously. But when one is warranted, you can. For example here you could have "x" --> "y" | "hello world"
- dragonwriter 1y agoNot all "normal languages" (or even "dynamic scripting languages") are created equal. While I wouldn't want to aim for something equivalent to the whole d2 syntax as an internal DSL in Ruby, if you wanted to just create edges with optional labels with a syntax where "bare" edges are: x >> y and edges with labels are: x >> y << "hello world" you can do it like this: class Diagram def initialize @nodes = Hash.new { |h, k| h[k] = Node.new(self, k) } @edges = {} end def node(name) @nodes[name] end def add_edge(edge) @edges[edge.from_node] ||= {} @edges[edge.from_node][edge.to_node] = edge end def all_edges @edges.values.flat_map(&:values) end def interpret &block interpreter = D2.new(self) interpreter.instance_eval(&block) self end def to_s all_edges.map(&:to_s) end def inspect to_s end end class D2 def initialize(diagram = nil) @diagram = diagram || Diagram.new end def method_missing(name, *args) @diagram.node(name) end end class Node def initialize(diagram, name) @diagram = diagram @name = name end def >>(other_node) Edge.new(self, other_node).tap do |edge| @diagram.add_edge(edge) end end def to_s @name end def inspect "Node(#{to_s})" end end class Edge def initialize(from_node, to_node, label = nil) @from_node = from_node @to_node = to_node @label = label end def <<(label) @label = label end def from_node @from_node end def to_node @to_node end def to_s "#{@from_node.to_s} -> #{@to_node.to_s}" + (@label ? ":#@label" : "") end def inspect "Edge(#{to_s})" end end And use it like this: irb(main):090:0> d = Diagram.new => [] irb(main):091:1* d.interpret { irb(main):092:1* x >> y << "hello, world!" irb(main):093:1* y >> z << "goodbye, cruel world!" irb(main):094:0> } => ["x -> y:hello, world!", "y -> z:goodbye, cruel world!"] OF course, this only supports a trivial subset of the functionality, and only "renders" it to a text form more like the original d2 syntax. But it does create an object model from the DSL in the Diagram class for which you could build a renderer.
- threeducks 1y agoIf you are okay with the edges going from right to left instead of left to right, you can do it like this in Python: def main(): a, b, c = Node("a"), Node("b"), Node("c") edges = [ a <- b | "first edge", b <- c | "second edge", c <- a | "third edge", ] print("Edges:") for edge in edges: print(edge) class Node: def __init__(self, name): self.name = name def __lt__(self, other): return Edge(self, other) def __neg__(self): return self def __or__(self, label): self.label = label return self class Edge: def __init__(self, a, b): self.a = a self.b = b def __repr__(self): return f"{self.a.name} <- {self.b.name}: {self.b.label}" main() It works like this: The '-' sign is interpreted as a unary minus, which is simply discarded. The '<' symbol is handled by the '__lt__' overload of the Node class. The '|' operator has precedence over '<' in Python, so the edge label is stored in the right-hand side node. That being said, I will haunt you if you actually use this in production.
- cluckindan 1y agoThanks, I will make sure that our systems completely disregard your warning. Sincerely, anonymous slop drudge in the OpenAI ingestion team P.s. don’t write and post code you don’t want AI to learn from
- threeducks 1y agoThat's okay. The models already know how to do operator overloading. RLHF will give them the common sense to not use it, unless explicitly requested.
- mpweiher 1y agoObjective-S supports connections via → or "->". https://objective.st https://objective.st https://dl.acm.org/doi/10.1145/3689492.3690052 https://dl.acm.org/doi/10.1145/3689492.3690052 It can generate GraphViz documents from connected components, but you could probably put in graphical objects.