TypeScript const assertions: how to use Array.prototype.includes? - typescript3.0

I am trying to use an array of elements as union type, something that became easy with const assertions in TS 3.4, so I can do this:
const CAPITAL_LETTERS = ['A', 'B', 'C', ..., 'Z'] as const;
type CapitalLetter = typeof CAPITAL_LETTERS[string];
Now I want to test whether a string is a capital letter, but the following fails with "not assignable to parameter of type":
let str: string;
...
CAPITAL_LETTERS.includes(str);
Is there any better way to fix this rather than casting CAPITAL_LETTERS to unknown and then to Array<string>?

The standard library signature for Array<T>.includes(u) assumes that the value to be checked is of the same or narrower type than the array's elements T. But in your case you are doing the opposite, checking against a value which is of a wider type. In fact, the only time you would say that Array<T>.includes<U>(x: U) is a mistake and must be prohibited is if there is no overlap between T and U (i.e., when T & U is never).
Now, if you're not going to be doing this sort of "opposite" use of includes() very often, and you want zero runtime efects, you should just widen CAPITAL_LETTERS to ReadonlyArray<string> via type assertion:
(CAPITAL_LETTERS as ReadonlyArray<string>).includes(str); // okay
If, on the other hand, you feel seriously enough that this use of includes() should be accepted with no type assertions, and you want it to happen in all of your code, you could merge in a custom declaration:
// global augmentation needed if your code is in a module
// if your code is not in a module, get rid of "declare global":
declare global {
interface ReadonlyArray<T> {
includes<U>(x: U & ((T & U) extends never ? never : unknown)): boolean;
}
}
That will make it so that an array (well, a readonly array, but that's what you have in this example) will allow any parameter for .includes() as long as there is some overlap between the array element type and the parameter type. Since string & CapitalLetter is not never, it will allow the call. It will still forbid CAPITAL_LETTERS.includes(123), though.
Okay, hope that helps; good luck!

Another way to solve it is with a type guard
https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards
const myConstArray = ["foo", "bar", "baz"] as const
function myFunc(x: string) {
//Argument of type 'string' is not assignable to parameter of type '"foo" | "bar" | "baz"'.
if (myConstArray.includes(x)) {
//Hey, a string could totally be one of those values! What gives, TS?
}
}
//get the string union type
type TMyConstArrayValue = typeof myConstArray[number]
//Make a type guard
//Here the "x is TMyConstArrayValue" tells TS that if this fn returns true then x is of that type
function isInMyConstArray(x: string): x is TMyConstArrayValue {
return myConstArray.includes(x as TMyConstArrayValue)
//Note the cast here, we're doing something TS things is unsafe but being explicit about it
//I like to this of type guards as saying to TS:
//"I promise that if this fn returns true then the variable is of the following type"
}
function myFunc2(x: string) {
if (isInMyConstArray(x)) {
//x is now "foo" | "bar" | "baz" as originally intended!
}
}
While you have to introduce another "unnecessary" function this ends up looking clean and working perfectly. In your case you would add
const CAPITAL_LETTERS = ['A', 'B', 'C', ..., 'Z'] as const;
type CapitalLetter = typeof CAPITAL_LETTERS[string];
function isCapitalLetter(x: string): x is CapitalLetter {
return CAPITAL_LETTERS.includes(x as CapitalLetter)
}
let str: string;
isCapitalLetter(str) //Now you have your comparison
//Not any more verbose than writing .includes inline
if(isCapitalLetter(str)){
//now str is of type CapitalLetter
}

Here's a solution that works well for strings & string literals using TypeScript 4.1 Template Literal Types that doesn't break anything else, and also narrows the type for convenience when used in conditions:
declare global {
interface ReadonlyArray<T> {
includes<S, R extends `${Extract<S, string>}`>(
this: ReadonlyArray<R>,
searchElement: S,
fromIndex?: number
): searchElement is R & S;
}
}
Originally posted by noppa in a TypeScript github issue related to this.

Adding to #imagio's answer, you can make the genetic type guard (thanks to #wprl for simplification)
function isIn<T>(values: readonly T[], x: any): x is T {
return values.includes(x);
}
And use it with any as const array:
const specialNumbers = [0, 1, 2, 3] as const;
function foo(n: number) {
if (isIn(specialNumbers, n)) {
//TypeScript will say that `s` has type `0 | 1 | 2 | 3` here
}
}

You can also create a curried version of Array.prototype.includes which works with tuples:
const PROPS = ['a', 'b', 'c'] as const;
const withTuple = <
List extends string[]
>(list: readonly [...List]) =>
(prop: string): prop is List[number] =>
list.includes(prop)
const includes = withTuple(PROPS);
const result = includes('d')
declare let str: string
if (includes(str)) {
str // "a" | "b" | "c"
}
Playground
Higher order function with list argument created for inference.
You can also check my article

Reassignment using a wider type annotation is potentially the simplest solution, if a little untidy due to adding an extraneous variable.
const CAPITAL_LETTERS = ['A', 'B', 'C', ..., 'Z'] as const;
const widenedCapitalLetters: string[] = CAPITAL_LETTERS
widenedCapitalLetters.includes("hello")
This allows you to keep the const assertion on the base array so you get the type narrowing you need.

using lodash
const CAPITAL_LETTERS = ['A', 'B', 'C', 'Z'] as const;
_.includes(CAPITAL_LETTERS, 'A');

Related

Type inference is dynamic on operator overloading with extension in Dart

I try to implement pipe-operator overriding |
extension Pipe on Object {
operator |(Function(Object) f) => f(this);
}
typedef Id = A Function<A>(A);
Id id = <A>(A a) => a;
var t1 = id("test"); // String t1
var t2 = "test" | id; // dynamic t2
with the generic Id function above, on id("test") is detected as String, but "test" | id is dynamic which is very problematic.
How can I fix this?
EDIT
Thankfully, #jamesdlin has answered and suggested:
extension Pipe on Object {
Object operator |(Object Function(Object) f) => f(this);
}
the result has improved as
var t2 = "test" | id; // Object t2
I also tried with generic as follows:
extension Pipe<A, B> on A {
B operator |(B Function(A) f) => f(this);
}
I expected it would go better because I thought the generic A B is more specific and better than Object ; however, the result goes as bad as before:
var t2 = "test" | id; // dynamic t2
Why the generic does not work? Is there any way to make the dart compiler infer it as string ?
Your operator | extension does not have a declared return type. Its return type therefore is implicitly dynamic. Also note its callback argument does not specify a return type either, so that also will be assumed to be dynamic.
Declare return types:
extension Pipe on Object {
Object operator |(Object Function(Object) f) => f(this);
}
(Answering your original question about a NoSuchMethodError: when you did
var x = "test" | id;
x | print;
x has type dynamic, but extension methods are static; they are compile-time syntactic sugar and will never work on dynamic types. Consequently, x | print attempts to call operator | on the object that x refers to, but that object doesn't actually have an operator |.)

what does [ ] mean in dart when used to wrap a parameter in a function

I have been writing dart for a while and stumbled on this line of code:
String makeCommand(String executable, [List<String>? arguments]) {
var result = executable;
if (arguments == null) return result;
return result + ' ' + arguments.join(' ');
}
the parameter [List<String>? arguments] confuses me because I am used to this {List<String>? arguments}. Can someone help me explain this?
In dart, if you give a function like this hello(int a, int b). You have to provide both a and b value if you are using this function or else you will get type error.
Now if the function is like this hello(int a , [int b]), the parameter b is optional now. You don't have to give the value of b and yet the function works when called by giving only one parameter.
Difference between hello(int a , [int b]) and hello(int a , {int b})
Valid Function call for hello(int a , [int b])
hello(1) //valid
hello(1,2) //valid
hello(1, b:2) // not valid
Valid Function call for hello(int a , {int b})
hello(1) // not valid
hello(1,2) // not valid
hello(1, b:2) // valid
The parameters enclosed in [] are optional positional parameters as shown in the docs
What that means is that you can write both makeCommand("some executable name") and makeCommand("some executable name", some list of arguments) without error

Why is `functionArgs` implemented twice? (i.e, as a primop and in `lib`)

Trying to understand callPackage, so looked up its implementation where it uses lib.functionArgs (source), but there is already a builtins.functionArgs primop, an alias of __functionArgs (implemented in C).
lib.functionArgs is defined as
/* Extract the expected function arguments from a function.
This works both with nix-native { a, b ? foo, ... }: style
functions and functions with args set with 'setFunctionArgs'. It
has the same return type and semantics as builtins.functionArgs.
setFunctionArgs : (a → b) → Map String Bool.
*/
functionArgs = f: f.__functionArgs or (builtins.functionArgs f);
and the __functionArgs attribute above is coming from setFunctionArgs (source):
/* Add metadata about expected function arguments to a function.
The metadata should match the format given by
builtins.functionArgs, i.e. a set from expected argument to a bool
representing whether that argument has a default or not.
setFunctionArgs : (a → b) → Map String Bool → (a → b)
This function is necessary because you can't dynamically create a
function of the { a, b ? foo, ... }: format, but some facilities
like callPackage expect to be able to query expected arguments.
*/
setFunctionArgs = f: args:
{
__functor = self: f;
__functionArgs = args;
};
I understand what setFunctionArgs does, and the comment above its declaration tells why it is necessary, but I can't understand it; both clauses of that sentence are clear but not sure how the first statement prevents the second one to be achieved (without setFunctionArgs, that is).
danbst also tried to elucidate this further,
lib.nix adds __functionArgs attr to mimic __functionArgs builtin. It
used to "pass" actual __functionArgs result down to consumers, because
builtin __functionArgs only works on top-most function args
but not sure what the "consumers" are, and couldn't unpack the last clause (i.e., "builtin __functionArgs only works on top-most function args"). Is this a reference to the fact that Nix functions are curried, and
nix-repl> g = a: { b, c }: "lofa"
nix-repl> builtins.functionArgs g
{ }
?
lib.functionArgs also doesn't solve this problem, but I'm probably off the tracks at this point.
Notes to self
__functor is documented in the Nix manual under Sets.
$ nix repl '<nixpkgs>'
Welcome to Nix version 2.3.6. Type :? for help.
Loading '<nixpkgs>'...
Added 11530 variables.
nix-repl> f = { a ? 7, b }: a + b
nix-repl> set_f = lib.setFunctionArgs f { b = 9; }
nix-repl> set_f
{ __functionArgs = { ... }; __functor = «lambda # /nix/store/16blhmppp9k6apz41gjlgr0arp88awyb-nixos-20.03.3258.86fa45b0ff1/nixos/lib/trivial.nix:318:19»; }
nix-repl> set_f.__functionArgs
{ b = 9; }
nix-repl> set_f set_f.__functionArgs
16
nix-repl> set_f { a = 27; b = 9; }
36
lib.functionArgs wraps builtins.functionArgs in order to provide reflective access to generic functions.
This supports reflection with builtins.functionArgs:
f = { a, b, c }: #...
Now consider the eta abstraction of the same function:
f' = attrs: f attrs
This does not support reflection with builtins.functionArgs. With setFunctionArgs, you can restore that information, as long as you also use lib.functionArgs.
I recommend to avoid reflection because everything that I've seen implemented with it can be implemented without it. It expands the definition of a function to include what should normally be considered implementation details.
Anyway, the primary motivation seems to be callPackage, which can be implemented with normal attrset operations if you change all packages to add ... as in { lib, stdenv, ... }:. I do have a morbid interest in this misfeature that is function reflection, so if anyone finds another use case, please comment.

what does 'T', 'f', 'E', 'e', '→' stand for in dart/flutter docs?

i`m learning the flutter, but i do not understand those letters meaning.
map<T>(T f(E e)) → Iterable<T>
Returns a new lazy Iterable with elements that are created by
calling f on each element of this Iterable in iteration order. [...]
so,what do they stand for?
T:
f:
E:
e:
→:
Iterable.map<T>:
map<T>(T f(E e)) → Iterable<T>
Returns a new lazy Iterable with elements that are created by calling
f on each element of this Iterable in iteration order. [...]
T is a language Type in this case the Type of the items of the iterable and is also the type that function f must return.
→ tells you the return type of the whole function (map) in this case an Iterable of T
f is the function applied to the Element e that is passed as the parameter to the function so that the function could do some operation with this current value and then return a new value of type T based on the value of the element e.
If you navigate the Iterable map function definition you will see that:
Iterable<T> map <T>(
T f(
E e
)
)
So I wanna sharpen my answer starting with the exact map<T> function of the OP and then swich to a more complex example.
Just to clarify all these let's take a concrete class of the Iterable class, the Set class choosing a Set of type String in such a scenario:
Set<String> mySet = Set();
for (int i=0; i++<5;) {
mySet.add(i.toString());
}
var myNewSet = mySet.map((currentValue) => (return "new" + currentValue));
for (var newValue in myNewSet) {
debugPrint(newValue);
}
Here I've got a Set of String Set<String> and I want another Set of String Set<String> so that the value is the same value of the original map, but sorrounded with a prefix of "new:". And for that we could easily use the map<T> along with the closure it wants as paraemters.
The function passed as closure is
(currentValue) => ("new:" + currentValue)
And if we want we could write it also like that:
(currentValue) {
return "new:" + currentValue;
}
or even pass a function like that:
String modifySetElement(String currentValue) {
return "new:" + currentValue;
}
var myNewSet = mySet.map((value) => ("new:" + value));
var myNewSet = mySet.map((value) {return "new:" + value;});
var myNewSet = mySet.map((value) => modifySetElement("new:" + value));
And this means that the parameter of the function (closure) is the String value of the element E of the Set we're modifying.
We don't even have to specify the type because its inferred by method definition, that's one of the power of generics.
The function (closure) will be applied to all the Set's elements once at a time, but you write it once as a closure.
So summarising:
T is String
E is the element we are dealing with inside of the function
f is our closure
Let's go deeper with a more complex example. We'll now deal with the Dart Map class.
Its map function is define like that:
map<K2, V2>(MapEntry<K2, V2> f(K key, V value)) → Map<K2, V2>
So in this case the previous first and third T is (K2, V2) and the return type of the function f (closure), that takes as element E parameter the pair K and V (that are the key and value of the current MapEntry element of the iteration), is a type of MapEntry<K2, V2> and is the previous second T.
The whole function then return a new Map<K2, V2>
The following is an actual example with Map:
Map<int, String> myMap = Map();
for (int i=0; i++<5;) {
myMap[i] = i.toString();
}
var myNewMap = myMap.map((key, value) => (MapEntry(key, "new:" + value)));
for (var mapNewEntry in myNewMap.entries) {
debugPrint(mapNewEntry.value);
}
In this example I've got a Map<int, String> and I want another Map<int, String> so that (like before) the value is the same value of the original map, but sorrounded with a prefix of "new:".
Again you could write the closure (your f function) also in this way (maybe it highlights better the fact that it's a fanction that create a brand new MapEntry based on the current map entry value).
var myNewMap = myMap.map((key, value) {
String newString = "new:" + value;
return MapEntry(key, newString);
});
All these symbols are called Generics because they are generic placeholder that correspond to a type or another based on the context you are using them.
That's an extract from the above link:
Using generic methods
Initially, Dart’s generic support was limited to classes. A newer syntax, called generic methods, allows
type arguments on methods and functions:
T first<T>(List<T> ts) {
// Do some initial work or error checking, then...
T tmp = ts[0];
// Do some additional checking or processing...
return tmp;
}
Here the generic type parameter on first () allows you to use the
type argument T in several places:
In the function’s return type (T). In the type of an argument
(List<T>). In the type of a local variable (T tmp).
Follow this link for Generics name conventions.

What do curly braces wrapping constructor arguments represent?

Consider the following piece of code:
class Person {
String id;
String name;
ConnectionFactory connectionFactory;
// What is this constructor doing?
Person({this.connectionFactory: _newDBConnection});
}
If you precede a constructor's argument with this, the corresponding field will be automatically initialized, but why {...}?
This makes the argument a named optional argument.
When you instantiate a Person you can
Person p;
p = new Person(); // default is _newDbConnection
p = new Person(connectionFactory: aConnectionFactoryInstance);
without {} the argument would be mandatory
with [] the argument would be an optional positional argument
// Constructor with positional optional argument
Person([this.connectionFactory = _newDBconnection]);
...
Person p;
p = new Person(); // same as above
p = new Person(aConnectionFactoryInstance); // you don't specify the parameter name
Named optional parameters are very convenient for boolean arguments (but of course for other cases too).
p = new Person(isAlive: true, isAdult: false, hasCar: false);
There is a specific order in which these argument types can be used:
mandatory (positional) arguments (only positional arguments can be mandatory)
optional positional arguments
(optional) named arguments (named arguments are always optional)
Note that positional and named optional arguments use a different delimiter for the default value.
The named requires : but the positional requires =. The language designers argue that the colon fits better with the Map literal syntax (I would at least have used the same delimiter for both).
= is supported as delimiter since Dart 2 and preferred according to the style guide while : is still supporzed.
See also:
What is the difference between named and optional parameters in Dart?
Functions Are Fun, Pt 1 - Dart Tips, Ep 6
Chapter 2. A Tour of the Dart Language - Functions
Chapter 2. A Tour of the Dart Language - Constructors
Dart functions allow positional parameters, named parameters, and optional positional and named parameters, or a combination of all of them.
Positional parameters are simply without decoration:
void debugger(String message, int lineNum) {
// ...
}
Named parameters means that when you call a function, you attach the argument to a label. This example calls a function with two named parameters:
debugger(message: 'A bug!', lineNum: 44);
Named parameters are written a bit differently. You wrap any named parameters in curly braces ({ }). This line defines a function with named parameters:
void debugger({String message, int lineNum}) {
Named parameters, by default, are optional. But you can annotate them and make them required:
Widget build({#required Widget child}) {
//...
}
Finally, you can pass positional parameters that are optional, using [ ]:
int addSomeNums(int x, int y, [int z]) {
int sum = x + y;
if (z != null) {
sum += z;
}
return sum;
}
You call that function like this:
addSomeNums(5, 4)
addSomeNums(5, 4, 3)
You can define default values for parameters with the = operator in the function signature, and the function can be simplified as below:
addSomeNums(int x, int y, [int z = 5]) => x + y + z;
the this. connectionFactory in
Person({this.connectionFactory: _newDBConnection});
is called Automatic Class Member Variable Initialization. See this example

Resources