Explicar las aserciones de tipo en Go
(3)
Respuesta corta
En una línea:
x.(T)
afirma quex
no es nulo y que el valor almacenado enx
es de tipoT
¿Por qué los usaría?
-
para comprobar
x
es nulo - para verificar si es convertible (afirmar) a otro tipo
- convertir (afirmar) a otro tipo
Qué devuelven exactamente:
-
t := x.(T)
=> t es de tipoT
; six
es nulo, entra en pánico. -
t,ok := x.(T)
=> six
es nulo o no del tipoT
=>ok
esfalse
contrario,ok
estrue
yt
es del tipoT
Explicación detallada
Imagine que necesita calcular el área de 4 formas diferentes: Círculo, Cuadrado, Rectángulo y Triángulo.
Puede definir nuevos tipos con un nuevo método llamado
Area()
, como este:
type Circle struct {
Radius float64
}
func (t Circle) Area() float64 {
return math.Pi * t.Radius * t.Radius
}
Y para
Triangle
:
type Triangle struct {
A, B, C float64 // lengths of the sides of a triangle.
}
func (t Triangle) Area() float64 {
p := (t.A + t.B + t.C) / 2.0 // perimeter half
return math.Sqrt(p * (p - t.A) * (p - t.B) * (p - t.C))
}
Y para
Rectangle
:
type Rectangle struct {
A, B float64
}
func (t Rectangle) Area() float64 {
return t.A * t.B
}
Y para
Square
:
type Square struct {
A float64
}
func (t Square) Area() float64 {
return t.A * t.A
}
Aquí tienes
Circle
, con radio de 1.0, y otras formas con sus lados:
shapes := []Shape{
Circle{1.0},
Square{1.772453},
Rectangle{5, 10},
Triangle{10, 4, 7},
}
¡Interesante!
¿Cómo podemos recogerlos todos en un solo lugar?
Primero necesita la
Shape interface
para recopilarlos todos en una rebanada de forma
[]Shape
:
type Shape interface {
Area() float64
}
Ahora puedes recogerlos así:
shapes := []Shape{
Circle{1.0},
Square{1.772453},
Rectangle{5, 10},
Triangle{10, 4, 7},
}
Después de todo,
Circle
es una
Shape
y
Triangle
es una
Shape
.
Ahora puede imprimir el área de cada forma usando la declaración individual
v.Area()
:
for _, v := range shapes {
fmt.Println(v, "/tArea:", v.Area())
}
Entonces
Area()
es una interfaz común entre todas las formas.
Ahora, ¿cómo podemos calcular y llamar a un método poco común como ángulos de triángulo usando las
shapes
anteriores?
func (t Triangle) Angles() []float64 {
return []float64{angle(t.B, t.C, t.A), angle(t.A, t.C, t.B), angle(t.A, t.B, t.C)}
}
func angle(a, b, c float64) float64 {
return math.Acos((a*a+b*b-c*c)/(2*a*b)) * 180.0 / math.Pi
}
Ahora es el momento de extraer el
Triangle
de las
shapes
anteriores:
for _, v := range shapes {
fmt.Println(v, "/tArea:", v.Area())
if t, ok := v.(Triangle); ok {
fmt.Println("Angles:", t.Angles())
}
}
Usando
t, ok := v.(Triangle)
solicitamos aserciones de tipo, lo que significa que le pedimos al compilador que intente convertir
v
de tipo
Shape
para escribir
Triangle
, de modo que si tiene éxito, el
ok
será
true
contrario
false
, y luego si es exitoso llamar a
t.Angles()
para calcular los tres ángulos del triángulo.
Esta es la salida:
Circle (Radius: 1) Area: 3.141592653589793
Square (Sides: 1.772453) Area: 3.1415896372090004
Rectangle (Sides: 5, 10) Area: 50
Triangle (Sides: 10, 4, 7) Area: 10.928746497197197
Angles: [128.68218745348943 18.194872338766785 33.12294020774379]
Y todo el código de muestra de trabajo:
package main
import "fmt"
import "math"
func main() {
shapes := []Shape{
Circle{1.0},
Square{1.772453},
Rectangle{5, 10},
Triangle{10, 4, 7},
}
for _, v := range shapes {
fmt.Println(v, "/tArea:", v.Area())
if t, ok := v.(Triangle); ok {
fmt.Println("Angles:", t.Angles())
}
}
}
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
type Triangle struct {
A, B, C float64 // lengths of the sides of a triangle.
}
type Rectangle struct {
A, B float64
}
type Square struct {
A float64
}
func (t Circle) Area() float64 {
return math.Pi * t.Radius * t.Radius
}
// Heron''s Formula for the area of a triangle
func (t Triangle) Area() float64 {
p := (t.A + t.B + t.C) / 2.0 // perimeter half
return math.Sqrt(p * (p - t.A) * (p - t.B) * (p - t.C))
}
func (t Rectangle) Area() float64 {
return t.A * t.B
}
func (t Square) Area() float64 {
return t.A * t.A
}
func (t Circle) String() string {
return fmt.Sprint("Circle (Radius: ", t.Radius, ")")
}
func (t Triangle) String() string {
return fmt.Sprint("Triangle (Sides: ", t.A, ", ", t.B, ", ", t.C, ")")
}
func (t Rectangle) String() string {
return fmt.Sprint("Rectangle (Sides: ", t.A, ", ", t.B, ")")
}
func (t Square) String() string {
return fmt.Sprint("Square (Sides: ", t.A, ")")
}
func (t Triangle) Angles() []float64 {
return []float64{angle(t.B, t.C, t.A), angle(t.A, t.C, t.B), angle(t.A, t.B, t.C)}
}
func angle(a, b, c float64) float64 {
return math.Acos((a*a+b*b-c*c)/(2*a*b)) * 180.0 / math.Pi
}
Ver también:
Para una expresión x de tipo de interfaz y un tipo T, la expresión primaria
x.(T)
afirma que x no es nulo y que el valor almacenado en x es de tipo T. La notación x. (T) se denomina aserción de tipo.
Más precisamente, si T no es un tipo de interfaz, x. (T) afirma que el tipo dinámico de x es idéntico al tipo T. En este caso, T debe implementar el tipo (interfaz) de x; de lo contrario, la aserción de tipo no es válida, ya que no es posible que x almacene un valor de tipo T. Si T es un tipo de interfaz, x. (T) afirma que el tipo dinámico de x implementa la interfaz T.
Si se mantiene la aserción de tipo, el valor de la expresión es el valor almacenado en x y su tipo es T. Si la aserción de tipo es falsa, se produce un pánico en tiempo de ejecución. En otras palabras, aunque el tipo dinámico de x solo se conoce en tiempo de ejecución, se sabe que el tipo de x. (T) es T en un programa correcto.
var x interface{} = 7 // x has dynamic type int and value 7 i := x.(int) // i has type int and value 7 type I interface { m() } var y I s := y.(string) // illegal: string does not implement I (missing method m) r := y.(io.Reader) // r has type io.Reader and y must implement both I and io.Reader
Una aserción de tipo utilizada en una asignación o inicialización de la forma especial
v, ok = x.(T) v, ok := x.(T) var v, ok = x.(T)
produce un valor booleano sin tipo adicional. El valor de ok es verdadero si la afirmación se cumple. De lo contrario, es falso y el valor de v es el valor cero para el tipo T. No se produce pánico en tiempo de ejecución en este caso .
EDITAR
Pregunta
: ¿Qué devuelve la aserción
x.(T)
cuando T es una
interface{}
y no un tipo concreto?
Respuesta
:
Afirma que x no es nulo y que el valor almacenado en x es de tipo T.
Por ejemplo, esto entra en pánico (compilación: éxito, ejecución:
panic: interface conversion: interface is nil, not interface {}
):
package main
func main() {
var i interface{} // nil
var _ = i.(interface{})
}
Y esto funciona (Ejecutar: OK):
package main
import "fmt"
func main() {
var i interface{} // nil
b, ok := i.(interface{})
fmt.Println(b, ok) // <nil> false
i = 2
c, ok := i.(interface{})
fmt.Println(c, ok) // 2 true
//var j int = c // cannot use c (type interface {}) as type int in assignment: need type assertion
//fmt.Println(j)
}
Salida:
<nil> false
2 true
NOTA:
aquí
c
es de tipo
interface {}
y no
int
.
Vea este código de muestra de trabajo con salidas comentadas:
package main
import "fmt"
func main() {
const fm = "''%T''/t''%#[1]v''/t''%[1]v''/t%v/n"
var i interface{}
b, ok := i.(interface{})
fmt.Printf(fm, b, ok) // ''<nil>'' ''<nil>'' ''<nil>'' false
i = 2
b, ok = i.(interface{})
fmt.Printf(fm, b, ok) // ''int'' ''2'' ''2'' true
i = "Hi"
b, ok = i.(interface{})
fmt.Printf(fm, b, ok) // ''string'' ''"Hi"'' ''Hi'' true
i = new(interface{})
b, ok = i.(interface{})
fmt.Printf(fm, b, ok) // ''*interface {}'' ''(*interface {})(0xc042004330)'' ''0xc042004330'' true
i = struct{}{}
b, ok = i.(interface{})
fmt.Printf(fm, b, ok) // ''struct {}'' ''struct {}{}'' ''{}'' true
i = fmt.Println
b, ok = i.(interface{})
fmt.Printf(fm, b, ok) // ''func(...interface {}) (int, error)'' ''(func(...interface {}) (int, error))(0x456740)'' ''0x456740'' true
i = Shape.Area
b, ok = i.(interface{})
fmt.Printf(fm, b, ok) // ''func(main.Shape) float64'' ''(func(main.Shape) float64)(0x401910)'' ''0x401910'' true
}
type Shape interface {
Area() float64
}
Estoy leyendo sobre las aserciones de tipo
x.(T)
en
The Go Programming Language
y no las entiendo.
Entiendo que hay diferentes escenarios:
- T es un tipo concreto o una interfaz
- Se pueden devolver uno (¿valor afirmado?) O dos (ok) valores
Esto es lo que no entiendo:
- ¿Por qué los usaría?
- ¿Qué regresan exactamente?
También busqué en Google el tema y todavía no lo entiendo.
Caso de uso común: verifique si el error devuelto es de tipo T.
https://golang.org/ref/spec#Type_assertions
Para una sola afirmación de valor de retorno: cuando falla, el programa entra en pánico.
Para una afirmación de dos valores de retorno: cuando falla, el segundo argumento se establece en falso y el programa no entra en pánico.
Una aserción de tipo es la notación x. (T) donde x es del tipo de interfaz y T es un tipo. Además, el valor real almacenado en x es de tipo T, y T debe satisfacer el tipo de interfaz de x.