Why are optional properties in an object required for a ReadOnly object? - flowtype

Here's my example code:
/* #flow */
type A = {
var1 : number,
}
type B = $ReadOnly<{
var1 : number,
var2? : string,
}>
let v : A = { var1: 1 };
let w : B = v;
And the flow error:
13: let w : B = v;
^ Cannot assign `v` to `w` because property `var2` is missing in `A` [1] but exists in object type [2].
References:
12: let v : A = { var1: 1 };
^ [1]
13: let w : B = v;
^ [2]
I understand that normally A cannot be casted as a B, because that would allow v.var2 to be assigned. My thought would that making B ReadOnly would prevent that case. So I'm not understanding the case at which this cast would be an issue.

The issue here is that
type A = {
var1: number,
};
means defines A as an object with a property var1 being a number and all other properties being unknown.
For example:
type A = {
var1: number,
};
type C = {
var1: number,
var2: boolean,
};
let c: C = { var1: 1, var2: true };
let a: A = c;
is valid, because the A type is compatible with C.
However, if we then added on a snippet like your code:
type B = {
var1: number,
var2?: string,
};
let b: B = a;
this would treat b.var2 as string|void when it is in fact a boolean because b === c.

Related

flow: cast mixed type to an array of shape

I need to cast a mixed type to a shaped array in flow. Tryflow link.
type aShapedArray = Array<{a:string}>;
//externally defined type
const transform = ():mixed => [{a: 'hello'}];
const b = transform();
if (Array.isArray(b)) {
const a: aShapedArray = b;
}
The error is
9: const a: aShapedArray = b;
^ array. Has some incompatible type argument with
9: const a: aShapedArray = b;
^ aShapedArray
Type argument `T` is incompatible:
7: const b = transform();
^ mixed. This type is incompatible with
2: type aShapedArray = Array<{a:string}>;
^ object type
Sadly the only answer I found is to add an any cast to the array. Which pretty much equivalent to flow: ignore this line.
/* #flow */
type aShapedArray = Array<{a:string}>;
//externally defined type
const transform = ():mixed => [{a: 'hello'}];
const b: any = transform();
if (Array.isArray(b)) {
const a: aShapedArray = b;
}
try flow: link

class vs type in Flow

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

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})

How to build a map of struct and append values to it using GO?

I tried many ways to build a map of struct and append values to it and I did not find any way to do it.
The keys of the map are strings. The struct is made of two parts: "x" integer and "y" a slice of strings.
The errors I face to build the map are (for m) :
main.go:11: syntax error: unexpected comma, expecting semicolon, newline, or }
When I try to add new keys and values to the map, the errors are:
go:33: syntax error: missing operand
package main
import "fmt"
type TEST struct {
x int
y []string
}
// none of these var gives the expected result
var m = map[string]*struct{x int, y []string}{"foo": {2, {"a", "b"}}, }
var m2 = map[string]struct{x int, y []string}{"foo": {2, {"a", "b"}}, }
var n = map[string]*struct{x int
y []string
}{"foo": {2, {"a", "b"}}, }
var o = map[string]*struct{
x int
y []string
}{"foo": {2, {"a", "b"}}, }
func main() {
m["foo"].x = 4
fmt.Println(m["foo"].x)
// how can I had a new key ?
m["toto"].x = 0
m["toto"] = {0, {"c", "d"}}
// and append the string "e" to {"c", "d"} ?
m["toto"].y = append(m["toto"].y, "e")
a := new(TEST)
a.x = 0
a.y = {"c", "d"}
m["toto"] = a
fmt.Println(m)
}
Here's a way to write it:
package main
import "fmt"
type TEST struct {
x int
y []string
}
var m = map[string]*TEST { "a": {2, []string{"a", "b"}} }
func main() {
a := new(TEST)
a.x = 0
a.y = []string{"c", "d"}
m["toto"] = a
fmt.Println(m)
}
Note: two types aren't the same just because their struct have identical fields.
Long story. If you for some reason prefer unnamed types you must be quite verbose in composite literal describing both types and values
var m = map[string]*struct {
x int
y []string
}{"foo": {2, []string{"a", "b"}}}
or with semicolons
var m = map[string]*struct {x int; y []string}{"foo": {2, []string{"a", "b"}}}
and without indirection
var m1 = map[string]struct {
x int
y []string
}{2, []string{"a", "b"}}}
To add new key
m["todo"] = &struct {
x int
y []string
}{0, []string{"c", "d"}}
You can also assign TEST struct but only without indirection because pointers *TEST and *youunnamedstruct are not assignable nevertheless structs having identical fields assignable themself
m1["todo"] = TEST{0, []string{"c", "d"}}
You can append only to indirect map struct field
m["todo"].y = append(m["todo"].y, "e")
because direct map struct fields are not addressable

Propositional Logic Valuation in SML

I'm trying to define a propositional logic valuation using SML structure. A valuation in propositional logic maps named variables (i.e., strings) to Boolean values.
Here is my signature:
signature VALUATION =
sig
type T
val empty: T
val set: T -> string -> bool -> T
val value_of: T -> string -> bool
val variables: T -> string list
val print: T -> unit
end;
Then I defined a matching structure:
structure Valuation :> VALUATION =
struct
type T = (string * bool) list
val empty = []
fun set C a b = (a, b) :: C
fun value_of [] x = false
| value_of ((a,b)::d) x = if x = a then b else value_of d x
fun variables [] = []
| variables ((a,b)::d) = a::(variables d )
fun print valuation =
(
List.app
(fn name => TextIO.print (name ^ " = " ^ Bool.toString (value_of valuation name) ^ "\n"))
(variables valuation);
TextIO.print "\n"
)
end;
So the valuations should look like [("s",true), ("c", false), ("a", false)]
But I can't declare like a structure valuation or make an instruction like: [("s",true)]: Valuation.T; When I tried to use the valuation in a function, I get errors like:
Can't unify (string * bool) list (*In Basis*) with
Valuation.T
Could someone help me? Thanks.
The type Valuation.T is opaque (hidden).
All you know about it is that it's called "T".
You can't do anything with it except through the VALUATION signature, and that signature makes no mention of lists.
You can only build Valuations using the constructors empty and set, and you must start with empty.
- val e = Valuation.empty;
val e = - : Valuation.T
- val v = Valuation.set e "x" true;
val v = - : Valuation.T
- val v2 = Valuation.set v "y" false;
val v2 = - : Valuation.T
- Valuation.value_of v2 "x";
val it = true : bool
- Valuation.variables v2;
val it = ["y","x"] : string list
- Valuation.print v2;
y = false
x = true
val it = () : unit
Note that every Valuation.T value is printed as "-" since the internal representation isn't exposed.

Resources