class vs type in Flow - flowtype

In Flow, why would one use a class versus a type?
type Point = {x: number; y: number};
class Point = {x: number; y: number};

In your example, a type is all you need.
But if you want to define methods, you'll want to use a class.
class Point {
x: number;
y: number;
constructor(x, y) {this.x = x; this.y = y;}
distance_from_origin(): number { /* code goes here */ }
angle_from_origin(): number { /* code goes here */ }
}
p: Point = new Point(2, 3);
d = p.distance_from_origin()
Types are a flow features for compile-time checking, to help you catch errors in your code. They are entirely stripped from the code before running it.
Classes aren't a Flow feature at all (Although Flow understands classes - every class you create also defines a Flow type) - they're a feature of ES6, the next version of JavaScript. Babel, the same program that strips Flow types from your code to make it valid JavaScript, can also convert your classes to ES5-compatible code.

Classes provide nominal typing, while object types provide structural typing.
Suppose I wanted to introduce a Vector type with x and y fields. When I go to create my add(p: Point, v: Vector): Point function, structural typing proves inadequate, e.g.
type Point = {x: number, y: number};
type Vector = {x: number, y: number};
function add(p: Point, v: Vector): Point {
return {x: p.x + v.x, y: p.y + v.y};
}
const p1: Point = {x:0, y:5};
const p2: Point = {x:2, y:3};
const v: Vector = add(p1, p2); // This is not an error in Flow
Contrast that against the nominally typed version with classes:
class Point { x: number; y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
class Vector { x: number; y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
function add(p: Point, v: Vector): Point {
return new Point(p.x + v.x, p.y + v.y);
}
const p1: Point = new Point(0, 5);
const p2: Point = new Point(2, 3);
const v: Vector = add(p1, p2); // Error: p2 isn't a Vector
(In reality you'd probably attach add as a method on the point class, but I've left it separate for parallel structure with the object type example.)
Note that you can use a tag field to get some semblance of nominal typing from object types, e.g.
type Point = { tag: "point", x: number y: number };
type Vector = { tag: "vector", x: number, y: number };
If your class doesn't have any methods, then I would suggest that this is the way to go.

As seen in the documentation, flow treats objects and classes differently. (And even if you are transpiring to ES5, flow checking happens before that.)
A class is compared by name. An object type is compared by structure.
In fact, the type alias is just a shorter way of writing the type structure annotation. It is like a variable containing the longer expression.
type A = {wow: number}
type B = {wow: number}
let a:A = {wow: 1}
let b:B = {wow: 2}
; [a, b] = [b, a] // fine
console.log(a, b) // > { wow: 2 } { wow: 1 }
class C {
constructor (x: number) { this.wow = x }
wow: number
}
class D {
constructor (x: number) { this.wow = x }
wow: number
}
let c:C = new C(3)
let d:D = new D(4)
c = d // ERROR: D This type is incompatible with C
d = b // ERROR: object type This type is incompatible with D
As you can see, two classes with identical structure are not compatible.
NOTE: There are ways to make different classes compatible, eg Union Types or Interfaces

Related

What does spread operator in type declaration do in Flow?

If I have two objects of partially matching shapes like
const point2d = { x: 0, y: 0 };
const point3d = { x: 0, y: 0, z: 0 };
then the valid type declaration in Flow would be
type Point2D = { x: number, y: number };
type Point3D = Point2D & { z: number };
At first, I tried to use the object spread operator and hit a problem quite soon because notation like
type Point3D = { ...Point2D, z: number };
is passed as valid but does not achieve the goal because in the end both x and y properties are missing from the Point3D type.
For example, I can do this with spread notation (which is wrong):
type Point2D = { x: number, y: number };
type Point3D = { ...Point2D, z: number };
const point2d: Point2D = { x: 0, y: 0 };
const point3d: Point3D = { y: 0, z: 0 }; // No errors
but cannot miss the x property in object declaration with type intersection notation:
type Point2D = { x: number, y: number };
type Point3D = Point2D & { z: number };
const point2d: Point2D = { x: 0, y: 0 };
const point3d: Point3D = { y: 0, z: 0 }; // Cannot assign object literal to `point3d` because property `x` is missing in object literal [1] but exists in `Point2D` [2].
Note that both cases are not exact shapes.
Is Flow's behavior in case of spread notation intentional in this case? Am I missing something?
See this issue.
The short version is that you can resolve basically all of these sorts of issues by making your objects exact. In general I've found that you'll have a much easier time with object types when making them exact as a rule unless you really don't want them to be exact for some reason. Also, $ReadOnly where applicable. Try

Object spread operator in Flow

I want to copy an object while changing only a single property. Without Flow, I could do this using the object spread operator like this:
class Point { x: number = 10; y: number = 10; }
const p1 = new Point();
const p2 = {...p1, y: 5};
But when I add type annotations to p1 and p2 like this:
const p1 = new Point();
const p2 = {...p1, y: 5};
I get the following error:
11: const p2:Point = {...p1, y: 5};
^^^^^^^^^^^^^ object literal. This type is incompatible with
11: const p2:Point = {...p1, y: 5};
^^^^^ Point
How would I achieve this type of operation in a type safe way in Flow?
As an example, in Elm, I can do this:
p2 = { p1 | y = 5 }
There must be some equivalent in Flow.
When you use object spread, you don't get an exact copy of an object. Instead, you get a plain object with all source object's properties copied. So, Flow is right here, p2 is not Point. Try this instead:
type Point = { x: number, y: number };
const p1: Point = { x: 10, y: 10 };
const p2: Point = { ...p1, y: 5 };
Explanation: class does not work because it uses nominal typing but type works because that uses structural typing.
If you (really) need a class instead of a type alias you can simulate the Elm syntax p2 = { p1 | y = 5 } by defining a constructor with only one argument
export class Point {
x: number = 10;
y: number = 10;
constructor(fields?: { x: number, y: number }) {
Object.assign(this, fields)
}
}
const p1 = new Point()
const p2: Point = new Point({...p1, y: 5})

panic: assignment to entry in nil map on single simple map

I was under the impression that the assignment to entry in nil map error would only happen if we would want to assign to a double map, that is, when a map on a deeper level is trying to be assigned while the higher one doesn't exist, e.g.:
var mm map[int]map[int]int
mm[1][2] = 3
But it also happens for a simple map (though with struct as a key):
package main
import "fmt"
type COO struct {
x int
y int
}
var neighbours map[COO][]COO
func main() {
for i := 0; i < 30; i++ {
for j := 0; j < 20; j++ {
var buds []COO
if i < 29 {
buds = append(buds, COO{x: i + 1, y: j})
}
if i > 0 {
buds = append(buds, COO{x: i - 1, y: j})
}
if j < 19 {
buds = append(buds, COO{x: i, y: j + 1})
}
if j > 0 {
buds = append(buds, COO{x: i, y: j - 1})
}
neighbours[COO{x: i, y: j}] = buds // <--- yields error
}
}
fmt.Println(neighbours)
}
What could be wrong?
You need to initialize neighbours: var neighbours = make(map[COO][]COO)
See the second section in: https://blog.golang.org/go-maps-in-action
You'll get a panic whenever you try to insert a value into a map that hasn't been initialized.
In Golang, everything is initialized to a zero value, it's the default value for uninitialized variables.
So, as it has been conceived, a map's zero value is nil. When trying to use an non-initialized map, it panics. (Kind of a null pointer exception)
Sometimes it can be useful, because if you know the zero value of something you don't have to initialize it explicitly:
var str string
str += "42"
fmt.Println(str)
// 42 ; A string zero value is ""
var i int
i++
fmt.Println(i)
// 1 ; An int zero value is 0
var b bool
b = !b
fmt.Println(b)
// true ; A bool zero value is false
If you have a Java background, that's the same thing: primitive types have a default value and objects are initialized to null;
Now, for more complex types like chan and map, the zero value is nil, that's why you have to use make to instantiate them. Pointers also have a nil zero value. The case of arrays and slice is a bit more tricky:
var a [2]int
fmt.Println(a)
// [0 0]
var b []int
fmt.Println(b)
// [] ; initialized to an empty slice
The compiler knows the length of the array (it cannot be changed) and its type, so it can already instantiate the right amount of memory. All of the values are initialized to their zero value (unlike C where you can have anything inside your array). For the slice, it is initialized to the empty slice [], so you can use append normally.
Now, for structs, it is the same as for arrays. Go creates a struct with all its fields initialized to zero values. It makes a deep initialization, example here:
type Point struct {
x int
y int
}
type Line struct {
a Point
b Point
}
func main() {
var line Line
// the %#v format prints Golang's deep representation of a value
fmt.Printf("%#v\n", line)
}
// main.Line{a:main.Point{x:0, y:0}, b:main.Point{x:0, y:0}}
Finally, the interface and func types are also initialized to nil.
That's really all there is to it. When working with complex types, you just have to remember to initialize them. The only exception is for arrays because you can't do make([2]int).
In your case, you have map of slice, so you need at least two steps to put something inside: Initialize the nested slice, and initialize the first map:
var buds []COO
neighbours := make(map[COO][]COO)
neighbours[COO{}] = buds
// alternative (shorter)
neighbours := make(map[COO][]COO)
// You have to use equal here because the type of neighbours[0] is known
neighbours[COO{}] = make([]COO, 0)

Declaring a pointer to a struct

I'm confused because there seem to be two ways to initialize a pointer to a struct in the go language and they appear to me to be somewhat opposite in logic.
var b *Vertex
var c &Vertex{3 3}
Why does one use a * and the other use a & if b and c have the same resulting type? My apologies for not adequately understanding the posts already up related to this topic.
I am also not yet straight on the implications of "receivers" in this context. The terminology I am familiar with is "reference to (a)" or "pointer to (a)" or "address of (a)" and "de-reference of" or "value at address".
Thanks in advance for your help.
There are a number of ways to declare a pointer to a struct and assign values to the struct fields. For example,
package main
import "fmt"
type Vertex struct {
X, Y float64
}
func main() {
{
var pv *Vertex
pv = new(Vertex)
pv.X = 4
pv.Y = 2
fmt.Println(pv)
}
{
var pv = new(Vertex)
pv.X = 4
pv.Y = 2
fmt.Println(pv)
}
{
pv := new(Vertex)
pv.X = 4
pv.Y = 2
fmt.Println(pv)
}
{
var pv = &Vertex{4, 2}
fmt.Println(pv)
}
{
pv := &Vertex{4, 2}
fmt.Println(pv)
}
}
Output:
&{4 2}
&{4 2}
&{4 2}
&{4 2}
&{4 2}
References:
The Go Programming Language Specification
Variable declarations
Short variable declarations
Address operators
Allocation
Composite literals
Receivers are used for methods. For example, v is the receiver for the Vertex Move Method.
package main
import "fmt"
type Vertex struct {
X, Y float64
}
func NewVertex(x, y float64) *Vertex {
return &Vertex{X: x, Y: y}
}
func (v *Vertex) Move(x, y float64) {
v.X = x
v.Y = y
}
func main() {
v := NewVertex(4, 2)
fmt.Println(v)
v.Move(42, 24)
fmt.Println(v)
}
Output:
&{4 2}
&{42 24}
References:
The Go Programming Language Specification
Method sets
Method declarations
Calls
Method expressions
Method values
var c = &Vertex{3, 3} (you do need the =) is declaring a struct and then getting the reference to it (it actually allocates the struct, then gets a reference (pointer) to that memory).
var b *Vertex is declaring b as a pointer to Vertex, but isn't initializing it at all. You'll have a nil pointer.
But yes, the types are the same.
You can also do:
var d *Vertex
d = &Vertex{3,3}
In addition to what Wes Freeman mentioned, you also asked about receivers.
Let say you have this:
type Vertex struct {
}
func (v *Vertex) Hello() {
... do something ...
}
The Vertex struct is the receiver for the func Hello(). So you can then do:
d := &Vertex{}
d.Hello()

Using a map for its set properties with user defined types

I'm trying to use the built-in map type as a set for a type of my own (Point, in this case). The problem is, when I assign a Point to the map, and then later create a new, but equal point and use it as a key, the map behaves as though that key is not in the map. Is this not possible to do?
// maptest.go
package main
import "fmt"
func main() {
set := make(map[*Point]bool)
printSet(set)
set[NewPoint(0, 0)] = true
printSet(set)
set[NewPoint(0, 2)] = true
printSet(set)
_, ok := set[NewPoint(3, 3)] // not in map
if !ok {
fmt.Print("correct error code for non existent element\n")
} else {
fmt.Print("incorrect error code for non existent element\n")
}
c, ok := set[NewPoint(0, 2)] // another one just like it already in map
if ok {
fmt.Print("correct error code for existent element\n") // should get this
} else {
fmt.Print("incorrect error code for existent element\n") // get this
}
fmt.Printf("c: %t\n", c)
}
func printSet(stuff map[*Point]bool) {
fmt.Print("Set:\n")
for k, v := range stuff {
fmt.Printf("%s: %t\n", k, v)
}
}
type Point struct {
row int
col int
}
func NewPoint(r, c int) *Point {
return &Point{r, c}
}
func (p *Point) String() string {
return fmt.Sprintf("{%d, %d}", p.row, p.col)
}
func (p *Point) Eq(o *Point) bool {
return p.row == o.row && p.col == o.col
}
package main
import "fmt"
type Point struct {
row int
col int
}
func main() {
p1 := &Point{1, 2}
p2 := &Point{1, 2}
fmt.Printf("p1: %p %v p2: %p %v\n", p1, *p1, p2, *p2)
s := make(map[*Point]bool)
s[p1] = true
s[p2] = true
fmt.Println("s:", s)
t := make(map[int64]*Point)
t[int64(p1.row)<<32+int64(p1.col)] = p1
t[int64(p2.row)<<32+int64(p2.col)] = p2
fmt.Println("t:", t)
}
Output:
p1: 0x7fc1def5e040 {1 2} p2: 0x7fc1def5e0f8 {1 2}
s: map[0x7fc1def5e0f8:true 0x7fc1def5e040:true]
t: map[4294967298:0x7fc1def5e0f8]
If we create pointers to two Points p1 and p2 with the same coordinates they point to different addresses.
s := make(map[*Point]bool) creates a map where the key is a pointer to the memory allocated to a Point and the value is boolean value. Therefore, if we assign elements p1 and p2 to the map s then we have two distinct map keys and two distinct map elements with the same coordinates.
t := make(map[int64]*Point) creates a map where the key is a composite of the coordinates of a Point and the value is a pointer to the Point coordinates. Therefore, if we assign elements p1 and p2 to the map t then we have two equal map keys and one map element with the shared coordinates.

Resources