Filtering out maybe types before accessing nullable property - flowtype

Given these two types:
type Point = [
number,
number,
];
type Some = {
a: Point,
b: ?Point,
};
And the data set:
const somes: Array<Some> = [
{a: [0, 1], b: [0, 2]},
{a: [2, 3], b: null}
]
Flow will automatically fail if we try to access somes[n].b.x given that b is a maybe type and might be either null or undefined.
We can however with confidence filter out all items in somes to exclude any item that does not include b:
const withB = somes.filter(s => !!s.b)
However flowtype will still complain when accessing items in withB as it doesn't pick up the exclusion:
console.log( withB.map(s => s.b[0]).join(',') )
// console.log(withB.map(s => s.b[0]).join(','))
// ^^^^^^ access of computed property/element. // Computed property/element cannot be accessed on possibly undefined value
// console.log(withB.map(s => s.b[0]).join(','))
// ^^^ undefined
Is it possible to somehow annotate or hint to flow that all items in withB are now guaranteed to include the b property?

Another option if you are willing to pay for additional computations
const withB = somes
.map(x => x.b ? { a: x.a, b: x.b } : null)
.filter(Boolean)

Here is the general way to hint Flow anything:
const withB: Array<{ a: Point, b: Point }> = (somes.filter(s => !!s.b): any)
It won't be safe in your case though. You have an array of mutable objects and property 'b' can be set to null at any time.

Related

How do I specify a type for a function parameter that optionally includes a given method?

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.

Handelbars find helper return object and assign key value to a template variable

I add a generic helper to find an item in an Array.
module.exports = function(array, findFunctionString) {
const fn = new Function("return" + findFunctionString)();
return array.find(fn)
};
My array is like :
[{label: "foo", selected: true}, {label: "bar", selected: false}]
What I'm looking now is to get the result and assign to a template variable with a specific key of this returned object.
{{#> myTemplate myVar=(find myArray "(el) => el.selected").label}}{{/myTemplate}}
I still got an error
Expecting 'CLOSE_RAW_BLOCK', 'CLOSE', 'CLOSE_UNESCAPED', 'OPEN_SEXPR', 'CLOSE_SEXPR', 'ID', 'OPEN_BLOCK_PARAMS', 'STRING', 'NUMBER', 'BOOLEAN', 'UNDEFINED', 'NULL', 'DATA', got 'SEP'
Yet, if remove ".label", no error, the object is well assigned to myVar. But I just want to assign the value of the key label.
Why can't I access to the key of the returned object ?
The solution was to use in addition lookup helper.
label=(lookup (find data.languageSelector.options "(el => el.selected)") "label")

map function not accepting stream of 'Subjects'

I am missing something obvious, but I can't see it
export const subjectSelector: MemoizedSelector<
any,
Subject[]
> = new EntitySelectorsFactory().create<Subject>('subject').selectEntities;
this.store.pipe(
select(entitySelectors.subjectSelector),
map((s:Subject) => return {...s, z: {}}),
filter((subject:Subject) => subject.z.evidence && subject.z.evidence.length > 0)
);
select(entitySelectors.subjectSelector) is returning an array of Subject objects, but the compiler complains
Type 'Subject' is missing the following properties from type 'Subject[]': length, pop, push, concat, and 28 more.
map((s:Subject) => return {...s, z: {}}),
What am I missing?
Seems like you are confusing list and Observable map() function. This works for me assuming selectEntities returns the Ngrx Entity type Dictionary. The Parenthteses are hell though:
this.store.pipe(select(subjectSelector),
map((subjects: Dictionary<Subject>) => Object.values(subjects).map(s => ({...s, z: {}}))));
if 'subjects' is just a plain array, this will do:
this.store.pipe(select(subjectSelector),
map((subjects: Subject[]) => subjects.map(s => ({...s, z: {}}))));

Generate a predicate out of two predicates (job for monoid, fold?)

I have two predicates
interface Foo {}
interface Bar {}
declare const isFoo: (a:unknown):a is Foo
declare const isBar: (a:unknown):a is Bar
What is the functional way to combine two predicates to create a new predicate (for simplicity, let's assume it's a => isFoo(a) && isBar(a)?
With fp-ts, I initially thought I could fold(monoidAll)([isFoo, isBar]), but fold expects the array to be of booleans, not of functions that evaluate to boolean.
This works
import { monoid as M, function as F, apply as A, identity as I, reader as R } from 'fp-ts'
interface Foo{}
interface Bar{}
declare const isFoo:(a:unknown) => a is Foo
declare const isBar:(a:unknown) => a is Bar
const isFooAndBar = F.pipe(A.sequenceT(R.reader)(isFoo, isBar), R.map(M.fold(M.monoidAll)))
But boy howdy is that convoluted. I thought there could be another way. I ended up writing my own monoid that takes two predicates and combines them, calling it monoidPredicateAll:
const monoidPredicateAll:M.Monoid<Predicate<unknown>> = {
empty: ()=>true,
concat: (x,y) => _ => x(_) && y(_)
}
Is there a canonical FP way of combining two predicates? I know I could do something like
xs.filter(x => isFoo(x) && isBar(x))
But it can get complicated with more predicates, and re-using a monoid makes it less likely I'll do a typo like isFoo(x) || isBar(x) && isBaz(x) when I meant all && (and that's where a xs.filter(fold(monoidPredicateAll)(isFoo,isBar,isBaz)) would help out.
I found a discussion about this on SO, but it was about Java and a built-in Predicate type, so didn't directly address my question.
Yes, I'm overthinking this :)
I ended up doing this:
export const monoidPredicateAll:Monoid<Predicate<unknown>> = {
empty: ()=>true,
concat: (x,y) => _ => x(_) && y(_)
}
Then I could do
import {monoid as M} from 'fp-ts'
declare const isFoo: Predicate<number>
declare const isBar: Predicate<number>
const isFooAndBar = M.fold(monoidPredicateAll)([isFoo,isBar])
For others looking for a working solution, based on #user1713450's answer
import * as P from 'fp-ts/lib/Predicate';
import * as M from 'fp-ts/Monoid';
const createMonoidPredicateAll = <T>(): M.Monoid<P.Predicate<T>> => ({
empty: () => true,
concat: (x, y) => (_) => x(_) && y(_),
});
export const combine = <T>(predicates: P.Predicate<T>[]) =>
M.concatAll(createMonoidPredicateAll<T>())(predicates);

Flow can't see that my array is not empty?

Considering the following code :
const size = 500
const indexes = new Array(size).fill(0).map((_,idx) => idx)
let vals
if (!vals) {
vals = new Array(size).fill(0)
}
indexes.forEach(v => vals[v]++)
Note: I understand this code might look strange. forEaching an array (indexes) and use value as index for another one (vals) might not be usual. But it works and I just tried to make a simple case of a real use-case.
Flow returns this error :
indexes.forEach(v => vals[v]++)
^ access of computed property/element. Computed property/element cannot be accessed on possibly undefined value
indexes.forEach(v => vals[v]++)
^ uninitialized variable
(You can try it on flow.org/try)
to remove the error, I have to change the last line to verify vals on each iteration :
indexes.forEach(v => vals && vals[v] && vals[v]++)
The error vanish also if I replace vals declaration with something initialised immediately
let vals = new Array(size).fill(0)
(So using an array values as indexes to another array doesn't seem to be a problem)
Shouldn't flow be able to understand that vals is defined ?
Thanks for any advice.
Flow is possibly not able to infer the value to that level so you could do like this
const size = 500
let vals
if (!vals) {
vals = new Array(size).fill(0)
}
let newVals = vals
vals.map(v => newVals[v]++)
The problem here isn't because flow thinks the array is empty. It's because flow assumes the conditional initialization is in fact conditional, whereas you know that the if branch will always be executed.
Flow is looking at the types, not the values. (Even though some of the values can be known at compile time)
As far as Flow is concerned,
if (<boolean expression here>) {
vals = new Array(size).fill(0)
}
may or may not initialize vals.
The easiest solution is to remove the unnecessary if to make it clear that vals is always initialized:
const size = 500
const indexes = new Array(size).fill(0).map((_,idx) => idx)
let vals
//if (!vals) {
vals = new Array(size).fill(0)
//}
indexes.forEach(v => vals[v]++)
Try it here
Or even better:
const size = 500
const indexes = new Array(size).fill(0).map((_,idx) => idx)
const vals = new Array(size).fill(0)
indexes.forEach(v => vals[v]++)
Try it here

Resources