tipos programacion por golang funciones for ejemplos constantes con ciclos bucle go constants

programacion - Golang: Creación de un tipo de constante y restricción de los valores del tipo



golang con ejemplos (2)

Tengo una pregunta sobre tipos de constantes que están restringidas a ciertos valores y cómo lo logras en Golang. Digamos que creo un tipo unary que tiene dos valores constantes Positive(1) y Negative(-1) y quiero restringir al usuario de ese tipo ( unary ) para que no cree otros valores de tipo unary . ¿Logro esto creando un paquete y haciendo que los valores Positive y Negative visibles y haciendo que el tipo unary restrinja al paquete que lo contiene? Ver el código a continuación, por ejemplo

package unary type unary int////not visible outside of the package unary const ( Positive unary = 1//visible outside of the package unary Negative unary = -1//visible outside of the package unary ) func (u unary) String() string {//visible outside of the package unary if u == Positive { return "+" } return "-" } func (u unary) CalExpr() int {//visible outside of the package unary if u == Positive { return 1 } return -1 }

¿Es esta la forma correcta de restringir un tipo a ciertos valores constantes?


Si le gusta simplemente trabajar con int sin introducir un tipo de contenedor: una forma clásica de hacerlo en Go es usar una interfaz pública con una función privada; para que todos puedan usarlo pero nadie puede implementarlo; me gusta:

type Unary interface { fmt.Stringer CalExpr() int disabler() //implementing this interface outside this package is disabled } var ( Positive Unary = unary(1) //visible outside of the package unary Negative Unary = unary(-1) //visible outside of the package unary ) type unary int //not visible outside of the package unary func (u unary) disabler() {} func (u unary) String() string { //visible outside of the package unary if u == Positive { return "+" } return "-" } func (u unary) CalExpr() int { //visible outside of the package unary if u == Positive { return 1 } return -1 }

Otros pueden establecer Positive a nil ; pero eso no es algo en el mundo Go, en tales casos.

Como mencionó @icza, uno puede sobrescribir los métodos públicos. Pero para los métodos privados, Go no llamará "el más superficial", sino que llamará al original.


Defectos

La solución propuesta no es segura de la manera que usted desea. Se pueden usar constantes de enteros sin tipo para crear nuevos valores de unary con un valor int diferente de 1 o -1 . Mira este ejemplo:

p := unary.Positive fmt.Printf("%v %d/n", p, p) p = 3 fmt.Printf("%v %d/n", p, p)

La salida será:

+ 1 - 3

Podríamos cambiar el valor de p para almacenar el valor int 3 que obviamente no es igual a Positive ni a Negative . Esto es posible porque Spec: Asignabilidad:

Un valor x se puede asignar a una variable de tipo T (" x se puede asignar a T ") en cualquiera de estos casos:

  • ...
  • x es una constante sin tipo representable por un valor de tipo T

3 es una constante sin tipo, y es representable por un valor de tipo unary que tiene el tipo subyacente int .

En Go no puede haber constantes "seguras" de las cuales los paquetes "outsider" no pueden crear nuevos valores de, por la razón mencionada anteriormente. Porque si desea declarar constantes en su paquete, solo puede usar expresiones que tengan versiones "sin tipo", que también pueden ser utilizadas por otros paquetes en asignaciones (como en nuestro ejemplo).

Estructura no exportada

Si desea cumplir con la parte "segura", puede usar struct expuestas, pero no pueden usarse en declaraciones constantes .

Ejemplo:

type unary struct { val int } var ( Positive = unary{1} Negative = unary{-1} ) func (u unary) String() string { if u == Positive { return "+" } return "-" } func (u unary) CalExpr() int { return u.val }

Intentando cambiar su valor:

p := unary.Positive p.val = 3 // Error: p.val undefied (cannot refer to unexported field or method val) p = unary.unary{3} // Error: cannot refer to unexported name unary.unary // Also error: implicit assignment of unexported field ''val'' in unary.unary literal

Tenga en cuenta que dado que ahora estamos usando una struct , podemos simplificar aún más nuestro código agregando la representación de string de nuestros valores a la struct :

type unary struct { val int str string } var ( Positive = unary{1, "+"} Negative = unary{-1, "-"} ) func (u unary) String() string { return u.str } func (u unary) CalExpr() int { return u.val }

Tenga en cuenta que esta solución todavía tiene un "defecto": utiliza variables globales exportadas, cuyos valores pueden ser modificados por otros paquetes. Es cierto que otros paquetes no pueden crear y asignar nuevos valores, pero pueden hacerlo con valores existentes, por ejemplo:

unary.Positive = unary.Negative

Si desea protegerse de dicho uso indebido, también debe hacer que las variables globales no se publiquen. Y luego, por supuesto, debe crear funciones exportadas para exponer esos valores, por ejemplo:

var ( positive = unary{1} negative = unary{-1} ) func Positive() unary { return positive } func Negative() unary { return negative }

Luego, adquirir / usar los valores:

p := unary.Positive()

Interfaz

Se debe tener cuidado si planea usar un tipo de interfaz para sus "constantes". Un ejemplo se puede ver en la respuesta de Kaveh Shahbazian. Se usa un método no exportado para evitar que otros implementen la interfaz, lo que le da la ilusión de que otros realmente no pueden implementarlo:

type Unary interface { fmt.Stringer CalExpr() int disabler() // implementing this interface outside this package is disabled } var ( Positive Unary = unary(1) // visible outside of the package unary Negative Unary = unary(-1) // visible outside of the package unary ) type unary int // not visible outside of the package unary func (u unary) disabler() {} func (u unary) String() string { /* ... */ } func (u unary) CalExpr() int { /* ... */ }

Este no es el caso, sin embargo. Con un truco sucio, esto puede ser evitado. El tipo Unary exportado se puede incrustar, y se puede usar un valor existente para implementar la interfaz (junto con el método no exportado), y podemos agregar nuestras propias implementaciones de los métodos exportados, haciendo / devolviendo lo que queramos.

Así es como puede verse:

type MyUn struct { unary.Unary } func (m MyUn) String() string { return "/" } func (m MyUn) CalExpr() int { return 3 }

Probándolo:

p := unary.Positive fmt.Printf("%v %d/n", p, p) p = MyUn{p} fmt.Printf("%v %d/n", p, p.CalExpr())

Salida:

+ 1 / 3

Caso especial

Como Volker mencionó en su comentario, en su caso especial podría usar

type unary bool const ( Positive unary = true Negative unary = false )

Como el tipo bool tiene dos valores posibles: true y false , y lo hemos usado todo. Entonces, no hay otros valores que puedan ser "explotados" para crear otros valores de nuestro tipo constante.

Pero sepa que esto solo se puede usar si el número de constantes es igual al número de valores posibles del tipo, por lo que la usabilidad de esta técnica es muy limitada.

También tenga en cuenta que esto no evita tales usos indebidos cuando se espera un tipo de unary , y alguien accidentalmente pasa una constante sin tipo como true o false .