warthog618 / go-gpiocdev

A native Go library for accessing GPIO lines on Linux platforms using the GPIO character device

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Why represent values as integers instead of booleans?

FeldrinH opened this issue · comments

The documentation states: "The values used throughout the API for line values are the logical value, which is 0 for inactive and 1 for active."
This seems to imply that there are only two valid values, so why represent the values as integers instead of booleans? It seems like a boolean would describe the set of valid values better, since with integers there are two valid values and a huge number of invalid values, whereas with a boolean there are no invalid values. Also a boolean just seems like a much more natural choice for representing logical values.

Allowing the use of 0 and 1 seemed more natural for users familiar with driving pins.

The documentation is correct for values returned by the API - they will only be 0 or 1. You can always pass in other values, but they are treated as 1.

Some of the underlying APIs use int as value, so keeping that type allows propagating the value without conversion.

bool is not really right either. Having to set a line to true is more verbose and confusing than setting it to 1.
You could provide other consts that map to true/false, but those would still be more verbose.

In short, using int seemed to provide better overall value than bool.

I guess this is ultimately subjective, but I strongly disagree. With booleans, it is immediately obvious just looking at the function signature what the possible input or output values are. With integers I have to consult the documentation. Also, the fact that I can pass random integers but they will just be treated as 1 feels dirty as far as API design is concerned. I'm a big believer in the idea that invalid or illogical inputs should be unrepresentable.

As for familiarity, the API-s I'm familiar with (in Python, Rust and Java) use booleans or custom enums with two values, so I find booleans more familiar and natural.
The only API-s I know that use integers are in C, but C doesn't have proper booleans, so the choice to use integers feels more like a technical limitation there.

On a more practical note: most values I want to output using GPIO pins are boolean values coming from elsewhere in my program. Since Go has no convenient built-in way to convert booleans to integers I am forced to litter my program with calls to a custom conversion function or extra if statements which both harm readability.

On a more practical note, this would be a breaking change.

Go doesn't have enums either - it uses consts. So define some consts with whatever name suits.

If it still bothers you then write a wrapper that does the conversion for you.

I guess I'll just have to live with the current API.

One thing I would recommend for the current API is mentioning directly in the function documentation that Value() returns only 0 and 1 and that SetValue() treats all values other than 0 and 1 as equivalent to 1. Currently this info is a little hard to find.

A wrapper around Line to provide functions that accept/return bool values could look like:

// Line adds bool methods to gpiocdev.Line
type Line struct {
	*gpiocdev.Line
}

func (l Line) active() (bool, error) {
	v, err := l.Value()
	if err != nil {
		return false, err
	}
	return v != 0, nil
}

func (l Line) setActive(active bool) error {
	return l.SetValue(btoi(active))
}

func btoi(b bool) int {
	if b {
		return 1
	}
	return 0
}

To use it, where l is returned by gpiocdev.RequestLine:

    bl := Line{l}
    active, err := bl.active()
    ...

And you could do similar for Lines.