I'm trying to use flow 0.53.1. Could you please help me explain this weird behavior?
This code sample:
/* #flow */
type AnySupportedType =
| AnySupportedPrimitive
| AnySupportedObject
| AnySupportedArray;
type AnySupportedArray = Array<AnySupportedType>;
type AnySupportedObject = { [string]: AnySupportedType };
type AnySupportedPrimitive = boolean | number | string | void;
type DataID = string
type Data = {
id: DataID
}
const y: Data = { id: "123" }
const x: AnySupportedType = y;
Renders this error:
17: const x: AnySupportedType = y;
^ object type. This type is incompatible with
17: const x: AnySupportedType = y;
^ union: AnySupportedPrimitive | AnySupportedObject | AnySupportedArray
Link to flow.org web-based example to play with.
Actually, this has to do with mutability. Flow cannot allow this code, since you could write x.id = 5 (after the appropriate type refinements), since the AnySupportedType type allows you to set any supported type, including a number as a property.
To solve this, you need to make the object properties covariant, effectively making them read-only:
type AnySupportedObject = { +[string]: AnySupportedType };
Note the addition of the +.
Once you do this, Flow allows the original assignment but prevents you from setting properties on x.
Check out the complete example on try.
See https://flow.org/blog/2016/10/04/Property-Variance/
The answer is that Flow has two ways to type Objects. One, your AnySupportedObject, treats the object as as dictionary where you can find an item by any key (similar to Map<string, whatever>.
The other way is as a record, where there are a specific set of known keys and each key can point to its own type of value (for example, {a: number, b: string}.
Those two types have very different meanings, though often either one can apply to a specific object. The type system keeps them distinct and forces you to treat an object in one way or the other to avoid generating type errors.
Related
Updated Question
I want to define a function named bsearch() to do binary searches against arrays of arbitrary object types. When I invoke the function, I want it to check whether or not the Type of the array contains a compare() method and use it, if it does. If it does not, I want it to fall back to using < and === (so it will work with strings and numbers).
What should the function declaration look like? (I don't need an actual implementation, just the syntax for a type-safe solution.)
Or maybe I'm going about this all wrong? How can I create a function that uses a method built into a parameter type if it exists, or use some other function when it doesn't?
Original Question
This is the original question, but I've replaced it with the above as it seems this wasn't getting my point across.
I want to define a function named bsearch() to do binary searches against arrays of arbitrary object types. So I'd like to do something like this:
type Comparator = <Type>(a: Type, b: Type) => -1 | 0 | 1;
static bsearch<Type extends { compare?: Comparator }>(
ary: Type[],
value: Type
): number { ... }
My goal is to specify that Type must extend a type that may or may not include the compare method. In my function, I will check whether the compare method exists on the value parameter and call if it does, or use a generic function (that uses < and ===) if it does not.
The definition of bsearch() does not produce any warnings or errors, but attempts to invoke it from my unit test does:
class Person {
name: string;
length: number;
compare: Comparator<Person>; // What goes here?
}
describe('Utils tests', () => {
const arrayOfInt = [10, 20, 30, 40];
const arrayOfStr = ['Alfred', 'Bob', 'Chuck'];
const arrayOfPersons: Person = [
{name:'Barney',length:2},
{name:'Fred',length:6}
{name:'Wilma',length:12},
];
it('can find integer in an array of integers', () => {
let search_for = 30;
let result = Utils.bsearch(arrayOfInt, search_for)
expect(result).to.be.equal(2);
});
it('can find string in an array of strings', () => {
let search_for = 'Bob';
let result = Utils.bsearch(arrayOfStr, search_for)
expect(result).to.be.equal(1);
});
it('can find Person in an array of Persons', () => {
// This one uses Person.compare() to do the search.
// The previous two tests used the fallback technique.
let search_for = {name:'Fred',length:6};
let result = Utils.bsearch(arrayOfPersons, search_for)
expect(result).to.be.equal(1);
});
});
The error message is:
TS2345: Argument of type 'number[]' is not assignable to parameter of type '{ compare?: Comparator | undefined; }[]'. Type 'number' has no properties in common with type '{ compare?: Comparator | undefined; }'.
I would appreciate pointers to other techniques if there is a better way to accomplish this (I'm still a TypeScript newbie).
Your generic is:
Type extends { compare?: Comparator }
Which means that Type must fulfill { compare?: Comparator } type. While passing object value, for example { name: 'Barney', length: 2, comparator: /* snip */}, is obviously correct, it's not the case for primitives like 10 and Bob. You need to include information about primitive types in the generic, for example:
Type extends ({ compare?: Comparator }) | number | string
Also, you'd probably want to enrich a bit the object typing:
{[key: string]: unknown, compare?: () => void } | number | string
Because, based on your description, you'd also want to accept also objects that do not have compare function in their type signature at all. If it does sound strange, I recommend reading about excess property checking.
I have a union type like this:
type ActionTypes = "ACTION_ONE" | "ACTION_TWO" | "ACTION_THREE"
And now I wonder if I can type that variable will be a string but none of the above?
for example:
const myStr: ActionTypes = "something" // no error
const myStr2: ActionTypes = "ACTION_ONE" // error
tl;dr: Maybe with type assertions, but it's hard to use effectively
I don't think there's a straightforward/possible way to exclude string literals from the string type. You might consider doing a type assertion of the variable by (ab)using $Call<F, T>, but this technique is almost certainly a bad idea:
(Try)
type ActionTypes = "ACTION_ONE" | "ACTION_TWO" | "ACTION_THREE"
type NonActionFuncType<T> =
(<T: ActionTypes>(T) => false) & (<T: string>(T) => true);
const good = "blah";
(true: $Call<NonActionFuncType<typeof good>, typeof good>) // Passes
const bad: "ACTION_ONE" = "ACTION_ONE";
(true: $Call<NonActionFuncType<typeof bad>, typeof bad>) // Fails
Pragmatically, I would suggest you look for another way to do whatever you're looking to do. Flow automatically types all string literals as string unless you specify the type, so this sort of technique won't catch too many bugs (unless you're passing variables with a literal string type, which you might be doing).
I am implementing binary search trees in OCaml, trying to use as much imperative programming as possible.
I have the following data type:
type tKey = Key of int;;
type tBST = Null | Pos of node ref
and node = {mutable key : tKey; mutable left : tBST; mutable right : tBST};;
I am having trouble with this function:
let createNode k tree =
tree := Pos ({key = k; left = Null; right = Null});;
Error: This record expression is expected to have type node ref
The field key does not belong to type ref
A binary search tree can be either Null (means empty tree) or a Pos. A tree Pos is a pointer to a node, and a node is a structure of a key and 2 other trees (left and right).
My main goal here is to have a tree that is modified after functions are over. Passing tree by reference so when createNode is over, the tBST I passed as parameter is modified.
Question: is actually possible to do what I am trying in OCaml? if so, how could I change my function createNode and/or data type to make this happen?
Thank you very much.
It is possible, but you need to create the Pos node with a reference explicitly:
Pos (ref {key = k; (*...*)})
Whether what you are trying to do is recommended practice in a language like Ocaml is a different story, though.
The question has already been answered. I would just like to add a side note: The use of ref seems superfluous in this case.
A value of type tBST is either Null or a mutable pointer. If it is Null it will remain Null. If it is non-Null, it will remain non-Null, but the actual pointer might change. That might well be what you intended, but I have my doubts. In particular, what tBST does not do, is to emulate C-style pointers (which are either null or really point somewhere). I suspect, though, that that was your intention.
The idiomatic way to emulate C-style pointers is to just use the built-in option type, like so:
type tBST = node option
A value of type node option is either None or Some n, where n is a pointer to a value of type node. You use tBST for mutable fields (of the record node), so you would effectively have mutable C-style pointers to nodes.
Here is what you probably had in mind:
type tree = node option ref
and node = {
mutable left: tree;
mutable key: int;
mutable right: tree;
};;
let t0 : tree = ref None;;
let t1 : tree = ref (Some { left = ref None; key = 1; right = ref None; }) ;;
let create_node key tree =
tree := Some { left = ref None; key; right = ref None; }
No need to have a separate type for key but you can if you want it, and with the latest OCaml there no runtime overhead for it.
I want to create a type that represents 2-character ISO country codes. Is there a simple way to create a type definition for these (or any other fixed-length item)?
Of course one approach would be an enum with the list of all the possible ones
type CountryCode = "AX" | "AL" | "DZ" | ...
But I'd love something simpler that just enforces 2-character strings.
Do I need to use dynamic type tests?
No, Flow does not have such a feature. It doesn't really have any dependent types features other perhaps types consisting individual values.
You could use tests like if (countryCode.length === 2) { ... } like you would normally do in JS, but Flow will not refine the type of countryCode inside the if block, because there is no Flow type to represent strings of two characters.
In contrast, with actual dynamic type tests like if (countryCode !== null) Flow will know that countryCode is not null inside the if block and can use that knowledge for type checking.
Note that technically you could generate a type that contains 26*26=676 strings of uppercase characters from A to Z: "AA", "AB", ..., "ZY", "ZZ", but that's not an improvement over your implementation.
Tuples are fixed length, so you could instead represent country codes as a tuple of two numbers (one for each character).
type CC = [number, number];
function fromString(str: string): CC {
if (str.length !== 2) {
throw new Error("not a cc: " + str);
}
return [
str.charCodeAt(0),
str.charCodeAt(1),
];
}
function toString(cc: CC): string {
return String.fromCharCode(cc[0]) + String.fromCharCode(cc[1]);
}
I have a type that can be used as a map key, but I want to prevent this from occurring. I assumed that if the type contained a private member it wouldn't be possible from other packages, but this appears to work anyway. What's the best way to make the type unusable as a map key?
type MyType struct {
A *A
b b
preventUseAsKey ?
}
I don't see any benefit of disallowing a type being used as a key. It is just an option which may or may not be used, the type will not be any better or smaller or faster just because you forbid to use it as a map key.
But if you want to do it: Spec: Map types:
The comparison operators == and != must be fully defined for operands of the key type; thus the key type must not be a function, map, or slice.
So if you can violate the terms of the comparison operators, you implicitly get what you want. You have a struct, terms for the struct types:
Struct values are comparable if all their fields are comparable. Two struct values are equal if their corresponding non-blank fields are equal.
So struct values are only comparable (and thus can only be used as keys in maps) if all their fields are comparable. Simply add a field whose type is not comparable.
Slice, map, and function values are not comparable.
So for example add a field whose type is a slice, and you're done:
type MyType struct {
S string
i int
notComparable []int
}
Attempting to use the above MyType as a key:
m := map[MyType]int{}
You get a compile-time error:
invalid map key type MyType
Note:
I wrote about not having any benefit of forbidding the type being a key. It's more than that: from now on you won't be able to use comparison operators on values of your type anymore (because of the extra, non-comparable field), so e.g. you lose the option to compare those values:
p1, p2 := MyType{}, MyType{}
fmt.Println(p1 == p2)
Compile-time error:
invalid operation: p1 == p2 (struct containing []int cannot be compared)
Note that with a little trick you could still preserve the comparable nature of your type, e.g. by not exporting your type but a wrapper type which embeds the original one; and add the extra, non-comparable type to the wrapper type, e.g.:
type myType struct {
S string
i int
}
type MyType struct {
myType
notComparable []int
}
func main() {
p1, p2 := MyType{}, MyType{}
fmt.Println(p1.myType == p2.myType)
}
This way your myType can remain comparable but still prevent the exported, wrapper MyType type to be used as key type.
Your type should not be comparable in order to be unfit as a map key.
Slice, map, and function values are not comparable
See Key Type:
Notably absent from the list are slices, maps, and functions; these types cannot be compared using ==, and may not be used as map keys.
So if your type is a slice, map or function, you should get what you need.
It could be an "alias" (defining a new named type):
type StringSliceWrap []string
type MyFunc func(i int)
That alias would not be used as a map key.
Update 2017: Brad Fitzpatrick give this tip (adding a slice in your struct) to make sure your type struct is not comparable: See play.golang.org:
package main
// disallowEqual is an uncomparable type.
// If you place it first in your struct, you prevent == from
// working on your struct without growing its size. (Don't put it
// at the end; that grows the size of the struct)
type disallowEqual [0]func()
type T struct {
_ disallowEqual
Foo string
Bar int
}
func main() {
var t1 T
var t2 T
println(t1 == t2)
}
T cannot be used as amp key now!