Is there any way to use flow to restrict specific string patterns? - reactjs

I'm using Flow on a React webapp and I'm currently facing a use-case where I'm asking for the user to input certain time values in a "HH:mm" format. Is there any way to describe what pattern is being followed by the strings?
I've been looking around for a solution but the general consensus which I agree to to a certain point seems to be that you don't need to handle this kind of thing using Flow, favouring using validating functions and relying on the UI code to supply the code following the correct pattern. Still, I was wondering if there is any way to achieve this in order to make the code as descriptive as possible.

You want to create a validator function, but enhanced using Opaque Type Aliases: https://flow.org/en/docs/types/opaque-types/
Or, more specifically, Opaque Type Aliases with Subtyping Constraints: https://flow.org/en/docs/types/opaque-types/#toc-subtyping-constraints
You should write a validator function in the same file where you define the opaque type. It will accept the primitive type as an argument and return a value typed as the opaque type with subtyping constraint.
Now, in a different file, you can type some variables as the opaque type, for example in function arguments. Flow will enforce that you only pass values that go through your validator function, but these could be used just as if they were the primitive type.
Example:
exports.js:
export opaque type ID: string = string;
function validateID(x: string): ID | void {
if ( /* some validity check passes */ ) {
return x;
}
return undefined;
}
import.js:
import type {ID} from './exports';
function formatID(x: ID): string {
return "ID: " + x; // Ok! IDs are strings.
}
function toID(x: string): ID {
return x; // Error: strings are not IDs.
}

Related

TypeScript Discriminated Union with Optional Discriminant

I've created a discriminated union that's later used to type props coming into a React component. A pared down sample case of what I've created looks like this:
type Client = {
kind?: 'client',
fn: (updatedIds: string[]) => void
};
type Server = {
kind: 'server',
fn: (selectionListId: string) => void
};
type Thing = Client | Server;
Note that the discriminant, kind, is optional in one code path, but is defaulted when it is destructured in the component definition:
function MyComponent(props: Thing) {
const {
kind = 'client',
fn
} = props;
if (kind === 'client') {
props.fn(['hey']);
// also an error:
// fn(['hey'])
} else {
props.fn('hi')
// also an error:
// fn('hey')
}
}
What I'm trying to understand is what's going on with this conditional. I understand that the type checker is having trouble properly narrowing the type of Thing, since the default value is separate from the type definition. The oddest part is that in both branches of the conditional it insists that fn is of type (arg0: string[] & string) => void and I don't understand why the type checker is trying to intersect the parameters here.
I would have expected it to be unhappy about non-exhaustiveness of the branches (i.e. not checking the undefined branch) or just an error on the else branch where the 'server' and undefined branches don't line up. But even trying to rearrange the code to be more explicit about each branch doesn't seem to make any impact.
Perhaps because the compiler simply can't narrow the types so tries an intersection so it doesn't matter which path--if the signatures are compatible then it's fine to call the function, otherwise you basically end up with a never (since string[] & string is an impossible type)?
I understand that there are a variety of ways I can resolve this via user-defined type predicates or type assertions, but I'm trying to get a better grasp on what's going on here internally and to find something a bit more elegant.
TS Playground link
It's an implementation detail in TS. Types are not narrowed when you are storing the value in a different variable. The same issue exists for the square bracket notation. You can refer to this question, it deals with a similar issue. Apparently, this is done for compiler performance.
You can fix your issue by using both props.fn and props.kind.
playground
Or write a type guard function.

Flow complains about type incompatibility even though I check type first

I have written a React dropdown component which I can supply either of the following to :
An array of strings
An array of simple JSON object each of which contains two properties of text and icon.
My simple flow types look as follows:
type DropdownMenuItemType = DropdownMenuIconAndTextType | string;
type DropdownMenuIconAndTextType ={
text: string,
icon?: React$Element<React$ElementType>;
}
Previous versions of the component only supports strings. The addition of an element to support text and an icon is a new feature request which I am in the process of implementing. I don't want any breaking changes for my existing users.
Therefore within my component I try to attempt to convert any string supplied and wrap it in a DropdownMenuIconAndTextType so everything ends up as this type. Items that are already DropdownMenuIconAndTextType just remain so.
let Array<DropdownMenuItemType> originalItems =
let Array<DropdownMenuIconAndTextType> convertedItems = [];
{'English', 'French', 'German', {text: 'Dutch', icon : <SomeIcon />}};
items.forEach( (currItem: DropdownMenuItemType) => {
if(typeof currItem === DropdownMenuIconAndTextType){
convertedItems.push(currItem);
}
else {
convertedItems.push({text: currItem.toString()});
}
});
However flow has one error with :
if(typeof currItem === DropdownMenuIconAndTextType){
convertedItems.push(currItem);
}
and it says that currItem could still be a string and is incompatible with convertedItems despite it being type checked as DropdownMenuIconAndTextType.
What do I need to do to satisfy flow in this scenario ? Thanks in advance.
I believe you're mixing up the distinction between Flow's type code and JS code.
Inside of type signatures, typeof returns the type of a literal value, as described here. In the JS code that exists at runtime, such as in your if statement, typeof will just tell you whether something is a string, object, etc., as described here. So the left side of your conditional operator will evaluate to either "string", or "object", not to the actual Flow type of the variable.
On the right side of your conditional, you have the Flow type DropdownMenuIconAndTextType, which only exists at type-checking time, not at runtime. I'm kind of surprised that Flow doesn't give you an error because of that.
Try something like this:
if(typeof currItem !== 'string'){
convertedItems.push(currItem);
}
This will check whether the value that exists at runtime is a string or an object, which should work with Flow's type refinements.

When writing a Lua-facing function in C, what's a good way to check if an argument supports table-like lookups?

Here's a potential pattern that can check if an argument is a table:
int my_fn(lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
// .. do stuff with the table ..
}
This works whenever the first argument is a table. However, other Lua types support table lookups, such as a userdata, and in luajit, a cdata.
Is there a nice way to check if a table lookup, such as via lua_getfield, will succeed before I call it? I mean without restricting the type to tables. Relatedly, are tables, userdata, and cdata the only types in luajit that support indexed lookups?
I'm most interested in answers restricted to the Lua 5.1 C API because I'm using LuaJIT which currently works with this version.
Clarification
The advantage of the luaL_checkXXX functions is that, in one line, they:
throw an informative, user-friendly error message if the type is wrong, and
provide a C-friendly return value that can be used immediately.
I'm looking for something similar for tables. I don't expect a C-friendly hash-table return value, but I do want the same quality of error message to the user if the argument in question is not indexable.
I'm embracing the philosophy of duck typing. If I write a function that simply wants to index some keys from an argument, then I don't care if that argument is truly a table, or just a userdata that supports __index lookups. I want to accept either one.
In general, just tables have lookups because it's the only type which defines this property. Userdata are opaque, just the host knows what to do with it or adds a metatable (which can be analyzed) for specific behaviour. CData are part of Lua compiling with LuaJIT, i never used this type with C API (is it even supported?). At the end you have to check the type/metatable for possible lookups and request a field to check for setting, there's no way around lua_getfield (but raw access should be faster, see lua_rawget). The exception would be to check for table array length by lua_objlen.
Furthermore a cheaper solution for type checking would be lua_is*** functions.
Here's one way to do it:
// If the value at index narg is not indexable, this function does not return and
// provides a user-friendly error message; otherwise the stack is unchanged.
static void luaL_checkindexable(lua_State *L, int narg) {
if (lua_istable(L, narg)) return; // tables are indexable.
if (!luaL_getmetafield(L, narg, "__index")) {
// This function will show the user narg and the Lua-visible function name.
luaL_argerror(L, narg, "expected an indexable value such as a table");
}
lua_pop(L, 1); // Pop the value of getmetable(narg).__index.
}
This works for tables and any value with an __index value on its metatable.
It provides a standard-format error given by luaL_argerror. Here's an example error message:
a_file.lua:7: bad argument #1 to 'fn' (expected an indexable value such as a table)
You can use it like this:
// This Lua-facing function expects an indexable 1st argument.
int my_fn(lua_State *L) {
luaL_checkindexable(L, 1);
lua_getfield(L, 1, "key"); // --> arg1.key or nil is now on top of stack.
// .. your fn ..
}

How do I create a Flow with a different input and output types for use inside of a graph?

I am making a custom sink by building a graph on the inside. Here is a broad simplification of my code to demonstrate my question:
def mySink: Sink[Int, Unit] = Sink() { implicit builder =>
val entrance = builder.add(Flow[Int].buffer(500, OverflowStrategy.backpressure))
val toString = builder.add(Flow[Int, String, Unit].map(_.toString))
val printSink = builder.add(Sink.foreach(elem => println(elem)))
builder.addEdge(entrance.out, toString.in)
builder.addEdge(toString.out, printSink.in)
entrance.in
}
The problem I am having is that while it is valid to create a Flow with the same input/output types with only a single type argument and no value argument like: Flow[Int] (which is all over the documentation) it is not valid to only supply two type parameters and zero value parameters.
According to the reference documentation for the Flow object the apply method I am looking for is defined as
def apply[I, O]()(block: (Builder[Unit]) ⇒ (Inlet[I], Outlet[O])): Flow[I, O, Unit]
and says
Creates a Flow by passing a FlowGraph.Builder to the given create function.
The create function is expected to return a pair of Inlet and Outlet which correspond to the created Flows input and output ports.
It seems like I need to deal with another level of graph builders when I am trying to make what I think is a very simple flow. Is there an easier and more concise way to create a Flow that changes the type of it's input and output that doesn't require messing with it's inside ports? If this is the right way to approach this problem, what would a solution look like?
BONUS: Why is it easy to make a Flow that doesn't change the type of its input from it's output?
If you want to specify both the input and the output type of a flow, you indeed need to use the apply method you found in the documentation. Using it, though, is done pretty much exactly the same as you already did.
Flow[String, Message]() { implicit b =>
import FlowGraph.Implicits._
val reverseString = b.add(Flow[String].map[String] { msg => msg.reverse })
val mapStringToMsg = b.add(Flow[String].map[Message]( x => TextMessage.Strict(x)))
// connect the graph
reverseString ~> mapStringToMsg
// expose ports
(reverseString.inlet, mapStringToMsg.outlet)
}
Instead of just returning the inlet, you return a tuple, with the inlet and the outlet. This flow can now we used (for instance inside another builder, or directly with runWith) with a specific Source or Sink.

Flink Scala API functions on generic parameters

It's a follow up question on Flink Scala API "not enough arguments".
I'd like to be able to pass Flink's DataSets around and do something with it, but the parameters to the dataset are generic.
Here's the problem I have now:
import org.apache.flink.api.scala.ExecutionEnvironment
import org.apache.flink.api.scala._
import scala.reflect.ClassTag
object TestFlink {
def main(args: Array[String]) {
val env = ExecutionEnvironment.getExecutionEnvironment
val text = env.fromElements(
"Who's there?",
"I think I hear them. Stand, ho! Who's there?")
val split = text.flatMap { _.toLowerCase.split("\\W+") filter { _.nonEmpty } }
id(split).print()
env.execute()
}
def id[K: ClassTag](ds: DataSet[K]): DataSet[K] = ds.map(r => r)
}
I have this error for ds.map(r => r):
Multiple markers at this line
- not enough arguments for method map: (implicit evidence$256: org.apache.flink.api.common.typeinfo.TypeInformation[K], implicit
evidence$257: scala.reflect.ClassTag[K])org.apache.flink.api.scala.DataSet[K]. Unspecified value parameters evidence$256, evidence$257.
- not enough arguments for method map: (implicit evidence$4: org.apache.flink.api.common.typeinfo.TypeInformation[K], implicit evidence
$5: scala.reflect.ClassTag[K])org.apache.flink.api.scala.DataSet[K]. Unspecified value parameters evidence$4, evidence$5.
- could not find implicit value for evidence parameter of type org.apache.flink.api.common.typeinfo.TypeInformation[K]
Of course, the id function here is just an example, and I'd like to be able to do something more complex with it.
How it can be solved?
you also need to have TypeInformation as a context bound on the K parameter, so:
def id[K: ClassTag: TypeInformation](ds: DataSet[K]): DataSet[K] = ds.map(r => r)
The reason is, that Flink analyses the types that you use in your program and creates a TypeInformation instance for each type you use. If you want to create generic operations then you need to make sure a TypeInformation of that type is available by adding a context bound. This way, the Scala compiler will make sure an instance is available at the call site of the generic function.

Resources