Can someone please explain the difference between "optional function parameters" and "maybe types", as explained on on this page of the Flow documentation?
The definitions sound very similar:
Maybe types: "Maybe types are for places where a value is optional"
Optional function parameters: "Functions can have optional parameters where a question mark ? comes after the parameter name."
I understand the differences from a syntax perspective. However, it sounds like both would be used in situations where you want to define an optional parameter for a function. Where would you use one over the other?
There isn't a difference. But also they're totally different things.
I think there's a bit of conceptual confusion here. Here's an example of an optional parameter:
function recase(str, lower) {
if (lower) {
return str.toLowerCase();
}
return str.toUpperCase();
}
recase('Test', true)
// "test"
recase('test')
// "TEST"
recase()
// Uncaught TypeError: Cannot read property 'toUpperCase' of undefined
Our function takes two arguments. The first one is required, if we don't pass at least one argument, the function will throw an exception. The second one is optional, if we don't pass the second one then no exception will be thrown, the value returned will just be different.
Note that I haven't introduced any types. This is because an "optional parameter" here is just a general programming concept. Flow doesn't have some intrinsic feature called "optional parameters." What flow does offer is a way to type optional parameters, called "maybe types."
So say I want to type my function above. Well, a first pass might look like this:
// We're taking a string and a boolean and returning a string, right?
function recase(str: string, lower: boolean): string {
if (lower) {
return str.toLowerCase();
}
return str.toUpperCase();
}
recase('Test', false)
// "TEST"
recase('Test', true)
// "test"
recase('Test')
// ^ Cannot call `recase` because function [1] requires another argument.
Since we typed lower
as boolean
, flow is expecting a boolean
to be passed as the second argument. When we don't pass a boolean, flow throws an error. Our parameter is no longer optional. We could just remove the type from lower
, but then flow would default lower
to the any
type, meaning the user could pass whatever they wanted which makes our types ambiguous and error-prone. Here's one thing we could do:
function recase(str: string, lower: void | boolean): string {
if (lower) {
return str.toLowerCase();
}
return str.toUpperCase();
}
recase('Test', true)
// "test"
recase('Test')
// "TEST"
In flow, the void
type only matches a value of undefined
. If we do not provide a value for lower
when calling recase
, then the value of lower
will be undefined
, and by typing lower as void | boolean
we have told flow that lower
can be either a boolean
or undefined
(not specified as a parameter).
So this is a very common scenario, obviously. So common in fact that at some point we might consider encapsulating it. This could be done with generics, like so:
// Let's call this Q for "Question" but it's nice and short
type Q<T> = void | null | T;
function recase(str: string, lower: Q<boolean>): string {
if (lower) {
return str.toLowerCase();
}
return str.toUpperCase();
}
Note that we've added null
to our generic type because the undefined
case overlaps so much with the null
case of wanting to be able to pass in null
for optional parameters.
Well, this is so common that flow offers us what amounts to a syntactic sugar for this situation, called "maybe types." If you were able to rename our Q
type to ?
then you would basically have maybe types.
function recase(str: string, lower: ?boolean): string {
if (lower) {
return str.toLowerCase();
}
return str.toUpperCase();
}