Negating types in Flow - flowtype

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

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.

ELM QueryString parser dont compile

I am really trying to learn a bit of ELM, but my mind collapse at the query parse, my idea was to create a function to get a query string value by name something like: given an query string ?name=Neuber a function like this getParam "name" that would return Neuber
But its failing at most basic example, it doesn't even compile
page comes from here
routeParser comes from here
module Main exposing (..)
-- import Url.Parser exposing (Parser, (</>), (<?>), oneOf, s)
import Url.Parser.Query exposing (int, map, map2, string)
type alias QueryParams =
{ search : Maybe String
, page : Maybe Int
}
routeParser : Url.Parser.Query.Parser QueryParams
routeParser = map2 QueryParams (string "search") (int "page")
page : Url.Parser.Query.Parser Int
page = map (Result.withDefault 1) (int "page")
The error i got
-- TYPE MISMATCH ---------------- /a/long/way/to/project/src/Main.elm
The 2nd argument to `map` is not what I expect:
15| page = map (Result.withDefault 1) (int "page")
^^^^^^^^^^
This `int` call produces:
Url.Parser.Query.Parser (Maybe Int)
But `map` needs the 2nd argument to be:
Url.Parser.Query.Parser (Result x number)
Hint: I always figure out the argument types from left to right. If an argument
is acceptable, I assume it is “correct” and move on. So the problem may actually
be in one of the previous arguments!
The immediate problem is that int "page" will return a Maybe Int, but you're trying to use it with Result.withDefault, which, as the error message says, expects a Result. The fix for this is just to use Maybe.withDefault instead.

What does the intersection of two function types boil down to?

Can someone point me to a comprehensive guide on the theory behind flowtype function intersections? Behavior is confusing to me. I understand that this type:
type FnT = ((string) => string) & ((number) => string);
reduces down to (string | number) => (string & string), but why is is that i can't cast the parameter to either string or number ???
i.e const g: FnT = (p: string) => { return "hi"; } gives me
Cannot assign function togbecause string [1] is incompatible with number [2] in the first argument..
Why??? isn't string a perfectly valid subtype of string | number?
is this because it expects a super type?
if this is the case then why is it that a union of same two function types lets me cast the param to one or the other?
i.e.
const FnT = ((string) => string) | ((number) => string) works with
const g: FnT = (p: string) => ("hi") ??? wouldn't we expect a supertype of string | number here?
With flow, you need to test all alternative types before casting.
example, if your type is string|number, and you want to cast as a number, you must first test that it is not actually a string.
This is because Flow will not try and modify your values for you, it is only a type checker. You must modify your values yourself, meaning flow can not 'convert' a number to a string, it can only cast the type.

How to infer literal type in Flow?

Is there a way to infer literal type with Flow in a generic way without explicit type casting?
Here's an example that should explain what I want to do. This code will not work with Flow because there's not $Literal utility type.
// explicit type casting to literal type
const a: 'flow' = 'flow
// inferring literal type
type Primitive = null | void | boolean | number | string
function literal <T: Primitive>(value: T): $Literal<T> {
return value
}
// cast type with generic function - type is inferred to 'flow' string literal
const a = literal('flow')

Recursive type definition in flow

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.

Resources