5
\$\begingroup\$

I'm trying to learn Golang from Java, and to do this, I wrote a basic circuit simulator with only two circuits. Is there anything I can do better to make it more idiomatic?

Simulator.go

package main

import (
    "fmt"
)

const ITERATIONS = 10

func main() {
    a := Wire{false, false}
    b := Wire{false, false}
    c := Wire{false, false}
    d := Wire{false, false}

    ga := NotGate{&a, &b}
    gb := XorGate{&b, &c, &d}

    wires := []*Wire{&a, &b, &c, &d}
    gates := []Gate{ga, gb}

    for i := 0; i < ITERATIONS; i++ {
        for _, w := range wires {
            fmt.Print("%d: {%t, %t}\n", i, w.BeginningValue, w.EndValue)
            w.Execute()
        }
        for _, g := range gates {
            g.Execute()
        }

    }
}

type Wire struct {
    BeginningValue bool
    EndValue       bool
}

func (wire *Wire) Execute() {
    wire.EndValue = wire.BeginningValue
}

type Gate interface {
    Execute()
}

XorGate.go

package main

import ()

type XorGate struct {
    A      *Wire
    B      *Wire
    Output *Wire
}

func (gate XorGate) Execute() {
    gate.Output.BeginningValue = gate.A.EndValue != gate.B.EndValue
}

NotGate.go

package main

import ()

type NotGate struct {
    A      *Wire
    Output *Wire
}

func (gate NotGate) Execute() {
    gate.Output.BeginningValue = !gate.A.EndValue
}
\$\endgroup\$
6
\$\begingroup\$

Your code is neat, and structured just like I would expect to see Java ;-) - separate files for each "class".

In your case, though, the separation in to files is probably over the top, and if you are talking idiomatic go, I would not expect to see separate files for the NotGate and the XorGate.

There is a small bug in your code, you use: fmt.Print("%d: {%t, %t}\n", i, w.BeginningValue, w.EndValue) where that should be a Printf and not a Print.

You have declared the Gate interface, and I understand why you have it, but there's a subtle thing about interfaces in go, they are not the same as Java. Go uses duck-typing, and, because you have an Execute method on the pointer to Wire, it is also actually a Gate too. You may not be expecting that.

You have an inconsistency in your pointers too. You keep your wires as pointers to your Wire instances, but you keep your gates as the actual Gate values.

I would expect that your Execute methods on the Notgate and XorGate should be on the pointer to them, not on the actual gate. Thus, your methods are currently:

func (gate XorGate) Execute() {
    gate.Output.BeginningValue = gate.A.EndValue != gate.B.EndValue
}

func (gate NotGate) Execute() {
    gate.Output.BeginningValue = !gate.A.EndValue
}

but should be:

func (gate *XorGate) Execute() {
    gate.Output.BeginningValue = gate.A.EndValue != gate.B.EndValue
}

func (gate *NotGate) Execute() {
    gate.Output.BeginningValue = !gate.A.EndValue
}

This makes the *XorGate and the *NotGate implement the Gate interface. Before it was the XorGate and NotGate that implemented it. I know, subtle difference, but, what this means is that your line of code:

gates := []Gate{ga, gb}

can be:

gates := []Gate{&ga, &gb}

The value of the above changes is that now you have consistency in the use of gates and wires.

Talking wires, your initialization of your various wires would be considered redundant in go - in go, it is idiomatic to ignore the specification of zero-values. The zero-value of a boolean is false, so specifying it is wrong. Your code:

a := Wire{false, false}
b := Wire{false, false}
c := Wire{false, false}
d := Wire{false, false}

Should just be:

a := Wire{}
b := Wire{}
c := Wire{}
d := Wire{}

Note, in the above, that a, b, etc. all are actual wires. It would also be idiomatic to resort to having everything in pointer-space, and use the new keyword.... new(...) returns a pointer to the value.

The result would be:

a := new(Wire)
b := new(Wire)
c := new(Wire)
d := new(Wire)

ga := &NotGate{a, b}
gb := &XorGate{b, c, d}

wires := []*Wire{a, b, c, d}
gates := []Gate{ga, gb}

Note, taking the pointer at the initialization of the NotGate and XorGate brings those in to pointer-space as well.

You are missing a bunch of documentation on the exported methods, types and constants ;-)

All told, I would expect your program to look like:

package main

import (
    "fmt"
)

const ITERATIONS = 10

type Wire struct {
    BeginningValue bool
    EndValue       bool
}

func (wire *Wire) Execute() {
    wire.EndValue = wire.BeginningValue
}

type Gate interface {
    Execute()
}

type XorGate struct {
    A      *Wire
    B      *Wire
    Output *Wire
}

func (gate *XorGate) Execute() {
    gate.Output.BeginningValue = gate.A.EndValue != gate.B.EndValue
}

type NotGate struct {
    A      *Wire
    Output *Wire
}

func (gate *NotGate) Execute() {
    gate.Output.BeginningValue = !gate.A.EndValue
}

func main() {
    a := new(Wire)
    b := new(Wire)
    c := new(Wire)
    d := new(Wire)

    ga := &NotGate{a, b}
    gb := &XorGate{b, c, d}

    wires := []*Wire{a, b, c, d}
    gates := []Gate{ga, gb}

    for i := 0; i < ITERATIONS; i++ {
        for _, w := range wires {
            fmt.Printf("%d: {%t, %t}\n", i, w.BeginningValue, w.EndValue)
            w.Execute()
        }
        for _, g := range gates {
            g.Execute()
        }

    }
}

See it in the playground here: https://play.golang.org/p/VdN4driYRn

Finally, I take some exception to the actual circuit nature of your code ;-) A Wire does not have different beginning and ending values - a wire is a single-state thing.... I am not sure why your values change on each iteration, I think your simulation is based on a broken understanding of logic. I would instead model the circuit as having a "Value()" and then find a way to chase back (recursively) what that Value would be when called.

\$\endgroup\$
  • \$\begingroup\$ Thank you for the great review! Also, the only reason I didn't do what you were proposing at the end is because of the case of circular connections. I wasn't really sure what the real world behavior was, but I'm sure it's not a stackoverflow exception. \$\endgroup\$ – Kyranstar Feb 26 '16 at 22:58
  • \$\begingroup\$ After switching to *Gate, doesn't the declaration change to gates := []*Gate{...} just as with wires? \$\endgroup\$ – David Harkness Mar 4 '16 at 18:16
  • \$\begingroup\$ @DavidHarkness Gate will be an interface after the change, so the pointer is sort of implied. \$\endgroup\$ – rolfl Mar 4 '16 at 19:55
  • \$\begingroup\$ I'm still just learning, but are you saying that because Gate is an interface you can assign two *XorGate (which conform to the interface) to a []Gate without declaring it []*Gate? Is there even a concept of a pointer to an interface, or does that not even make sense? Separating the idea of interfaces from Java is probably going to take some practice. :) \$\endgroup\$ – David Harkness Mar 6 '16 at 20:40
  • \$\begingroup\$ @DavidHarkness : See play.golang.org/p/T7sLjGFBgc and ping me in the 2nd if you want to chat ;-) \$\endgroup\$ – rolfl Mar 6 '16 at 23:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.