F.getWithDefault Coercion type problem
Hello everyone, first : thanks for this incredible library, look like the best in functionnal programmin in Ts
In the basic example
const res = pipe(
[1, 2, 3, 4, 5], // → [1, 2, 3, 4, 5]
A.dropExactly(2), // → Some([3, 4, 5])
O.flatMap(A.head), // → Some(3)
O.map(N.multiply(10)), // → Some(30)
) // → 30
O.getWithDefault(0)(res) // → 30
I have an typescript error at the last line:
Argument of type 'Option<number>' is not assignable to parameter of type 'Option<0>'.
Type 'number' is not assignable to type 'Option<0>'.ts(2345)
The only way I have to fix that is to explicit precise the type like: O.getWithDefault(0 as number)(res) // → 30
The version not curried works:
O.getWithDefault(res, 0)
I guess it's not normal so what is the reason and the solution?
Here my .eslintrc :
{ "extends": "@antfu", "plugins": [ "functional" ], "rules": { "@typescript-eslint/quotes": 0, "no-console": "off", "array-callback-return": "error", "no-constructor-return": "error", "no-duplicate-imports": "off", "no-new-native-nonconstructor": "error", "no-self-compare": "error", "no-template-curly-in-string": "error", "no-unused-private-class-members": "error", "class-methods-use-this": "error", "consistent-return": "error", "default-case": "error", "dot-notation": "error", "eqeqeq": "error", "init-declarations": "error", "no-eq-null": "error", "no-extend-native": "error", "no-implicit-coercion": "error", "@typescript-eslint/no-unused-vars": "off", "no-implicit-globals": "error", "no-new": "error", "no-new-func": "error", "no-new-object": "error", "no-var": "error", "prefer-object-spread": "error", "require-await": "error", "yoda": "error", "explicit-function-return-type": "off" } }
tsconfig.json:
{ "compilerOptions": { "noUnusedLocals": true, "noUnusedParameters": true, "baseUrl": ".", "module": "ESNext", "target": "ESNext", "lib": [ "DOM", "ESNext", "WebWorker" ], "strict": true, "esModuleInterop": true, "jsx": "preserve", "skipLibCheck": true, "moduleResolution": "node", "resolveJsonModule": true, "incremental": false, "noImplicitOverride": true, "noImplicitAny": true, "noImplicitThis": true, "strictFunctionTypes": true, "alwaysStrict": true, "strictBindCallApply": true, "strictPropertyInitialization": true, "useUnknownInCatchVariables": true, "allowUnusedLabels": false, "allowUnreachableCode": false, "noUncheckedIndexedAccess": true, "noPropertyAccessFromIndexSignature": true, "noFallthroughCasesInSwitch": true, "exactOptionalPropertyTypes": true, "noImplicitReturns": true, "strictNullChecks": true, "allowJs": false, "forceConsistentCasingInFileNames": true, "types": [ "vitest", "vite/client", "vue/ref-macros", "vite-plugin-pages/client", "vite-plugin-vue-layouts/client", ], "paths": { "~/": [ "src/" ] } }, "exclude": [ "dist", "node_modules", "cypress", "auto-imports.d.ts" ] }
Thank you in advance for your attention
give type parameter to O.getWithDefault. like
O.getWithDefault<number>(0).
@JUSTIVE Yes, I'm familiar with this solution, but I'd like to know if there's a way to improve this function to have this default behavior.
@mattaiod you're issue above is because O.getWithDefault expects 0 to be an Option<number> type but it is a literal integer, take a look at the signature for why the non-curried version works:
export declare function getWithDefault<A>(option: Option<A>, defaultValue: NonNullable<A>): A;
export declare function getWithDefault<A>(defaultValue: NonNullable<A>): (option: Option<A>) => A;
As you see, the function would explicitly type A as 0 in the curried version whereas it would resolve to just number in the other, because normally the curried value would be within the pipe already and that would define the type. Basically don't use the curried version outside of a pipe/flow sequence OR type it yourself.
Note, there are a lot of instances where you need to do this when using getWithDefault. For example, the following would be a type error because the default is never[]
pipe(O.fromNullable([1, 2, 3]), O.getWithDefault([]))
How about making the default type an extended type of the given T type when it's curried?
I just made a proof-of-concept version of this.
https://www.typescriptlang.org/play?strictNullChecks=false&jsx=0&ts=5.4.0-dev.20240121#code/JYWwDg9gTgLgBAbzMMBTANAeQL5wGZQQhwBEAAiBAEbAA2AngPQwDOAtFarTCQFAz00cTGBjAIAOwA8AFQB8cALxwZcAD5wJAV1q11cLRIAmqPMAmojvXo0YBjLVCioJ8UGFqoQLmAEMxkryoAB6QsHB2kizwAOaoMADqwDAAFgAipr468MqycgAUJnhZ3ABqvrRaqABcAHKStTq0vlSeeQCUigXB1SIB0vKdcrxwo3DBAPw9RSUw5ZWo1rZghJAslnDunt6u-uISQaHQ8JES0XBxicnpmdkATEpwsugAGnAhMC5GLHD1Eo26FpteSKP4A5qtVB5AozbLzKrVF5DfI9Pr7DpdEZjSbTW5lCpVawCIQASU+xGUJF8JH0JCoNLUJDsfBs9kczlcm3A2x8e0CyDQ+SxmAAdABlIiofJUkjtdCs0YAPQmWMuSVSGWK2WlzPaWNsyt4etZKwgaw2Wy8vP6vAFUuF4slUjJXgKMrlCrghtGauumtmdx1stZhvaQA
Maybe the NoInfer utility type from the Typescript 5.4.0 would be solution for this.
updated example above, seems just working as intended