Given the following type:
/* #flow */
type Vehicle = {
make?: string,
model?: string,
};
const vehicle: Vehicle = {
make: 'Ford',
model: 'Focus',
};
If I wanted to make sure that the make and model attributes were defined before using them I would expect the following to work:
const hasAttributes = vehicle.make && vehicle.model;
if (hasAttributes) {
console.log(`${vehicle.make} ${vehicle.model}`);
}
However flow throws an error saying that make and model could be undefined and so can't be coerced into a string.
This code does work however:
if (vehicle.make && vehicle.model) {
console.log(`${vehicle.make} ${vehicle.model}`);
}
Is this a bug or am I missing something? Here's
playground example.
There are two caveats here. First, most visible, is with this line...
const hasAttributes = vehicle.make && vehicle.model;
... which doesn't do what you expect it to do. Instead of returning true or false, && operator returns either the first operand, if it's falsy, or the last one. That's why hasAttributes value will never be strictly equal to true in your case (as both make and model properties are strings). But with === true you check for strict equality.
Ironically, Flow doesn't spot this glitch - because it's not its task. But you can make it more explicit, for example:
const hasAttributes = vehicle.make && vehicle.model;
(hasAttributes: boolean);
... and now you'll see the corresponding warning. The fix is rather trivial:
const hasAttributes = Boolean(vehicle.make && vehicle.model);
Still, it won't fix another, real problem. Flow is spotting the potential output of undefined values because it's somewhat pessimistic in these situations. There's a whole slew of similar issues on Flow's Github.
The point is, using a separate variable confuses the checker. That's why two all but identical snippets:
const hasAttributes = vehicle.make && vehicle.model;
if (hasAttributes) { // output vehicle
... and ...
if (vehicle.make && vehicle.model) { // output vehicle
... are treated differently.
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.
Map<String,bool> map= { "key1":true, "key2":false };
/*
* Flags following compilation error:
* A nullable expression can't be used as a condition.
* Try checking that the value isn't 'null' before using it as a condition.
*/
if(map["key1"]) {
//do sth
}
/*So I try checking value isn't null as specified in error
*Still flags same compilation error
*/
if(map!=null && map["key1"]) {
//do sth
}
//This works
if(map["key1"] == true) {
//do sth
}
}
Based on the following snippet, may I know why both the 1st and 2nd if blocks fail but not the 3rd?
You misunderstood the error message.
A nullable expression can't be used as a condition.
means that you can't do:
bool? condition;
if (condition) {
...
}
Map<K, V>'s operator[] returns a V?. It returns a nullable type as a way of indicating failure when the key isn't found, and you need to check that the returned value isn't null, not that map itself is not null. For example:
if (map["key"] ?? false) {
...
}
Your third approach (which checks == true) works because it will perform a null == true equality check if the lookup returns null. However, you should prefer using ?? false since it conveys the intent better, and equality checks against true or false are usually a code smell.
The [] operator on Map can return null which makes it nullable which is explained in details here: https://dart.dev/null-safety/understanding-null-safety#the-map-index-operator-is-nullable
So your first example is invalid since null is not a bool. So you cannot directly use the value from the [] operator for a Map.
Your second example is invalid for the same reason since map["key1"] is bool?.
Third example works since null == true is always false. So it is fully valid to make a comparison which involves something which can be null.
I frequently use the following pattern to create objects with null/undefined properties omitted:
const whatever = {
something: true,
...(a ? { a } : null),
...(b ? { b } : null),
};
As of flow release v0.112, this leads to the error message:
Computing object literal [1] may lead to an exponentially large number of cases to reason about because conditional [2] and conditional [3] are both unions. Please use at most one union type per spread to simplify reasoning about the spread result. You may be able to get rid of a union by specifying a more general type that captures all of the branches of the union.
It sounds to me like this isn't really a type error, just that Flow is trying to avoid some heavier computation. This has led to dozens of flow errors in my project that I need to address somehow. Is there some elegant way to provide better type information for these? I'd prefer not to modify the logic of the code, I believe that it works the way that I need it to (unless someone has a more elegant solution here as well). Asking here before I resort to // $FlowFixMe for all of these.
Complete example on Try Flow
It's not as elegant to write, and I think Flow should handle the case that you've shown, but if you still want Flow to type check it you could try rewriting it like this:
/* #flow */
type A = {
cat: number,
};
type B = {
dog: string,
}
type Built = {
something: boolean,
a?: A,
b?: B,
};
function buildObj(a?: ?A, b?: ?B): Built {
const ret: Built = {
something: true
};
if(a) ret.a = a
if(b) ret.b = b
return ret;
}
Try Flow
What is the idiomatic way to access properties of union type that may be missing in one of the types merged in the union?
type DataColumn = {
value: number;
};
type CalculatedColumn = {
calculation: string;
};
type Column = DataColumn | CalculatedColumn;
function getValue(c: Column) {
return c.value || c.calculation;
}
Flow typecheck results in the following error:
13: return c.value || c.calculation;
^ property `calculation`. Property not found in
13: return c.value || c.calculation;
^ object type
#dfkaye pointed out on twitter that if there is an error thrown for the "default" case, it works:
function e() {
throw new Error('foo');
}
function getValue(c: Column) {
return c.value || c.calculation || e();
}
Can somebody explain:
Why it works? Is it intentional, or a side effect?
Why is it necessary? Column type has always either value or calculation, so error case should never happen.
Is there a better, more idiomatic way?
Is this a safe approach, or is it likely to break in future?
PS: Seems like in TypeScript it can be done using type assertions.
The idiomatic way is to use disjoint unions. This passes with no errors:
type DataColumn = {
kind: 'data';
value: number;
};
type CalculatedColumn = {
kind: 'calculated';
calculation: string;
};
type Column = DataColumn | CalculatedColumn;
function e() {
throw new Error('foo');
}
function getValue(c: Column) {
return c.kind === 'data' ? c.value : c.calculation;
}
getValue({kind: 'data', value: 123});
getValue({kind: 'calculated', calculation: 'foo'});
I'm not actually sure why the case you described doesn't work. I can't think of any reason it would be unsound. But disjoint unions definitely work.
Why it works? Is it intentional, or a side effect?
It's most likely a bug, Flow simple ignores all branches but last:
function getValue(c: Column) {
return c.value || c.calculation || undefined;
}
Why is it necessary? Column type has always either value or calculation, so error case should never happen
This is where you are wrong. If value has a type { value: number } it means that it can have any other property of any type, including calculation of type string or may be of some other type.
Is there a better, more idiomatic way?
Yes, see Nat Mote's answer
Is this a safe approach, or is it likely to break in future?
It's not safe in principle, so it's very likely to break in the future
Seems like in TypeScript it can be done using type assertions.
You can do the same thing in Flow, but it's unsafe:
function getValue(c: Column) {
return ((c: any): DataColumn).value || ((c: any): CalculatedColumn).calculation;
}
Also you should not forget that numbers and string can be falsey.
I have my_list that defined this way:
struct my_struct {
comparator[2] : list of int(bits:16);
something_else[2] : list of uint(bits:16);
};
...
my_list[10] : list of my_struct;
It is forbidden to comparators at the same index (0 or 1) to be the same in all the list. When I constrain it this way (e.g. for index 0):
keep my_list.all_different(it.comparator[0]);
I get compilation error:
*** Error: GEN_NO_GENERATABLE_NOTIF:
Constraint without any generatable element.
...
keep my_list.all_different(it.comparator[0]);
How can I generate them all different? Appreciate any help
It also works in one go:
keep for each (elem) in my_list {
elem.comparator[0] not in my_list[0..max(0, index-1)].apply(.comparator[0]);
elem.comparator[1] not in my_list[0..max(0, index-1)].apply(.comparator[1]);
};
When you reference my_list.comparator it doesn't do what you think it does. What happens is that it concatenates all comparator lists into one bit 20 element list. Try it out by removing your constraint and printing it:
extend sys {
my_list[10] : list of my_struct;
run() is also {
print my_list.comparator;
};
};
What you can do in this case is construct your own list of comparator[0] elements:
extend sys {
comparators0 : list of int;
keep comparators0.size() == my_list.size();
keep for each (comp) in comparators0 {
comp == my_list.comparator[index * 2];
};
keep comparators0.all_different(it);
// just to make sure that we've sliced the appropriate elements
run() is also {
print my_list[0].comparator[0], comparators0[0];
print my_list[1].comparator[0], comparators0[1];
print my_list[2].comparator[0], comparators0[2];
};
};
You can apply an all_different() constraint on this new list. To make sure it's working, adding the following constraint should cause a contradiction:
extend sys {
// this constraint should cause a contradiction
keep my_list[0].comparator[0] == my_list[1].comparator[0];
};