@gcanti - there is currently a ton of boilerplate when writing functions dealing with Type Classes like Apply/etc. I spent a bit of time tinkering with some helper types as I've detailed below, any thoughts?
import { Apply, Apply1, Apply2, Apply2C, Apply3, Apply3C, sequenceT } from 'fp-ts/lib/Apply'
import { log } from 'fp-ts/lib/Console'
import { Either, either } from 'fp-ts/lib/Either'
import { tuple } from 'fp-ts/lib/function'
import { HKT, Type, Type2, Type3, URIS, URIS2, URIS3 } from 'fp-ts/lib/HKT'
import { io } from 'fp-ts/lib/IO'
export type URIOf<F> = F extends { URI: infer A } ? A : never
export type UnknownApply<L = unknown, U = unknown> =
| { [URI in URIS]: Apply1<URI> }[URIS]
| { [URI in URIS2]: Apply2<URI> }[URIS2]
| { [URI in URIS2]: Apply2C<URI, L> }[URIS2]
| { [URI in URIS3]: Apply3<URI> }[URIS3]
| { [URI in URIS3]: Apply3C<URI, U, L> }[URIS3]
| Apply<unknown>
export type TypeOfApply<T, A, L = any, U = any> = T extends Apply1<infer URI>
? Type<URI, A>
: T extends Apply2<infer URI>
? Type2<URI, L, A>
: T extends Apply2C<infer URI, infer L2>
? Type2<URI, L2, A>
: T extends Apply3<infer URI>
? Type3<URI, U, L, A>
: T extends Apply3C<infer URI, infer U2, infer L2>
? Type3<URI, U2, L2, A>
: T extends Apply<infer URI>
? HKT<URI, A>
: never
export type AOf<T> = T extends { _A: infer A } ? A : never
export type MapAOf<T extends Array<any>> = { [k in keyof T]: AOf<T[k]> }
export type LOf<T> = T extends { _L: infer L } ? L : never
export type UOf<T> = T extends { _U: infer U } ? U : never
export type AOfArray<T extends any[]> = T extends Array<infer A> ? A : never
export function call<F extends UnknownApply>(
F: F
): <TS extends TypeOfApply<F, any>[]>(
...fs: TS
) => <B>(f: (...as: MapAOf<TS>) => B) => TypeOfApply<F, B, LOf<AOfArray<TS>>, UOf<AOfArray<TS>>>
export function call<F extends Apply<any>>(F: F) {
return <T extends HKT<URIOf<F>, any>, TS extends T[]>(...ts: TS) => <B>(
f: (...as: MapAOf<TS>) => B
): HKT<URIOf<F>, B> => F.map(sequenceT(F)(...(ts as any)), as => f(...(as as any)))
}
const callIO = call(io)
const callEither = call(either)
const ok = <A>(a: A): Either<Error, A> => either.of<Error, A>(a)
const a = callIO(io.of('foo'), io.of(2))(tuple) // IO<[string, number]>
const b = callEither(ok('foo'), ok(2))(tuple) // Either<Error, [string, number]>
a.chain(log).run() // ['foo', 2]
b.fold(log, log).run() // ['foo', 2]
Apologies for the mixed use of any/unknown - should we implement, defaults should always use unknown
@christianbradley which typescript version are you using? Line 47
export function call<F extends UnknownApply>(
raises the following error
Overload signature is not compatible with function implementation.ts(2394)
with [email protected] and [email protected]
Currently using 3.3.4 on my end - here's my tsconfig.json
{
"compilerOptions": {
"target": "es2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
"rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
"strictNullChecks": true /* Enable strict null checks. */,
"strictFunctionTypes": true /* Enable strict checking of function types. */,
"strictBindCallApply": true /* Enable strict 'bind', 'call', and 'apply' methods on functions. */,
"strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */,
"noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
"alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */,
/* Additional Checks */
"noUnusedLocals": true /* Report errors on unused locals. */,
"noUnusedParameters": true /* Report errors on unused parameters. */,
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
/* Module Resolution Options */
"esModuleInterop": false /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
}
}
Also, using [email protected]
Here's a quick rewrite - found a couple inconsistencies, and corrected unnecessary uses of any
Let me know if this works.
import { Apply, Apply1, Apply2, Apply2C, Apply3, Apply3C, sequenceT } from 'fp-ts/lib/Apply'
import { log } from 'fp-ts/lib/Console'
import { Either, either } from 'fp-ts/lib/Either'
import { tuple } from 'fp-ts/lib/function'
import { HKT, Type, Type2, Type3, URIS, URIS2, URIS3 } from 'fp-ts/lib/HKT'
import { io } from 'fp-ts/lib/IO'
export type URIOf<F> = F extends { URI: infer A } ? A : never
export type UnknownApply<L = unknown, U = unknown> =
| { [URI in URIS]: Apply1<URI> }[URIS]
| { [URI in URIS2]: Apply2<URI> }[URIS2]
| { [URI in URIS2]: Apply2C<URI, L> }[URIS2]
| { [URI in URIS3]: Apply3<URI> }[URIS3]
| { [URI in URIS3]: Apply3C<URI, U, L> }[URIS3]
| Apply<unknown>
export type TypeOfApply<T, A, L = unknown, U = unknown> = T extends Apply1<infer URI>
? Type<URI, A>
: T extends Apply2<infer URI>
? Type2<URI, L, A>
: T extends Apply2C<infer URI, infer L2>
? Type2<URI, L2, A>
: T extends Apply3<infer URI>
? Type3<URI, U, L, A>
: T extends Apply3C<infer URI, infer U2, infer L2>
? Type3<URI, U2, L2, A>
: T extends Apply<infer URI>
? HKT<URI, A>
: never
export type AOf<T> = T extends { _A: infer A } ? A : never
export type MapAOf<T extends Array<unknown>> = { [k in keyof T]: AOf<T[k]> }
export type LOf<T> = T extends { _L: infer L } ? L : never
export type UOf<T> = T extends { _U: infer U } ? U : never
export type AOfArray<T extends unknown[]> = T extends Array<infer A> ? A : never
export function call<F extends UnknownApply>(
F: F
): <TS extends TypeOfApply<F, unknown>[]>(
...fs: TS & { 0: TypeOfApply<F, unknown> }
) => <B>(f: (...as: MapAOf<TS>) => B) => TypeOfApply<F, B, LOf<AOfArray<TS>>, UOf<AOfArray<TS>>>
export function call<F extends Apply<any>>(F: F) {
return <TS extends HKT<URIOf<F>, any>[]>(...ts: TS & { 0: HKT<URIOf<F>, any> }) => <B>(
f: (...as: MapAOf<TS>) => B
): HKT<URIOf<F>, B> => F.map(sequenceT(F)(...(ts as any)), as => f(...(as as any)))
}
const callIO = call(io)
const callEither = call(either)
const ok = <A>(a: A): Either<Error, A> => either.of<Error, A>(a)
const a = callIO(io.of('foo'), io.of(2))(tuple) // IO<[string, number]>
const b = callEither(ok('foo'), ok(2))(tuple) // Either<Error, [string, number]>
a.chain(log).run() // ['foo', 2]
b.fold(log, log).run() // ['foo', 2]
@christianbradley ok thanks, starting with a clean project doesn't give me any error, let me investigate..
@christianbradley I got it, in the project where I first tried your snippet I have the following declaration
declare module 'fp-ts/lib/HKT' {
interface URI2HKT<A> {
Response: Response<A>
}
}
export interface Response<A> {
readonly url: string
readonly status: number
readonly headers: Record<string, string>
readonly body: A
}
and looks like your implementation relies on the presence of the _A, _URI fields
Here's a rewrite with two updates:
1 - TypeParamsOf avoids reliance upon _A, _URI - uses inference from all known types
2 - By passing required first param, allow inference of L, U, X etc from usage - this is really helpful as the current implementation does not do this, and manually passing the type parameters requires passing the full args type as well
I added sequenceTv2 as an example as well
import { Apply, Apply1, Apply2, Apply2C, Apply3, Apply3C, sequenceT } from 'fp-ts/lib/Apply'
import { log } from 'fp-ts/lib/Console'
import { either, left } from 'fp-ts/lib/Either'
import { tuple } from 'fp-ts/lib/function'
import { HKT, HKT2, HKT3, HKT4, Type, Type2, Type3, Type4, URIS, URIS2, URIS3 } from 'fp-ts/lib/HKT'
import { io } from 'fp-ts/lib/IO'
export type TypeParamsOf<T> = T extends Type<infer URI, infer A>
? [URI, A, never, never, never]
: T extends Type2<infer URI, infer L, infer A>
? [URI, A, L, never, never]
: T extends Type3<infer URI, infer U, infer L, infer A>
? [URI, A, L, U, never]
: T extends Type4<infer URI, infer X, infer U, infer L, infer A>
? [URI, A, L, U, X]
: T extends HKT<infer URI, infer A>
? [URI, A, never, never, never]
: T extends HKT2<infer URI, infer L, infer A>
? [URI, A, L, never, never]
: T extends HKT3<infer URI, infer U, infer L, infer A>
? [URI, A, L, U, never]
: T extends HKT4<infer URI, infer X, infer U, infer L, infer A>
? [URI, A, L, U, X]
: [never, never, never, never, never]
export type URIOf<T> = TypeParamsOf<T>[0]
export type AOf<T> = TypeParamsOf<T>[1]
export type LOf<T> = TypeParamsOf<T>[2]
export type UOf<T> = TypeParamsOf<T>[3]
export type XOf<T> = TypeParamsOf<T>[4]
export type UnknownApply<L = unknown, U = unknown> =
| { [URI in URIS]: Apply1<URI> }[URIS]
| { [URI in URIS2]: Apply2<URI> }[URIS2]
| { [URI in URIS2]: Apply2C<URI, L> }[URIS2]
| { [URI in URIS3]: Apply3<URI> }[URIS3]
| { [URI in URIS3]: Apply3C<URI, U, L> }[URIS3]
| Apply<unknown>
export type TypeOfApply<T, A, L = unknown, U = unknown> = T extends Apply1<infer URI>
? Type<URI, A>
: T extends Apply2<infer URI>
? Type2<URI, L, A>
: T extends Apply2C<infer URI, infer L2>
? Type2<URI, L2, A>
: T extends Apply3<infer URI>
? Type3<URI, U, L, A>
: T extends Apply3C<infer URI, infer U2, infer L2>
? Type3<URI, U2, L2, A>
: T extends Apply<infer URI>
? HKT<URI, A>
: never
export type MapAOf<T> = { [k in keyof T]: AOf<T[k]> }
export type AOfArray<T extends unknown[]> = T extends Array<infer A> ? A : never
export type DomainOf<T extends Function> = T extends (...args: infer T) => any ? T : never
export function sequenceTv2<F extends UnknownApply>(
F: F
): <TS extends TypeOfApply<F, any, L, U>[], A, L, U>(
fa: TypeOfApply<F, A, L, U>,
...ts: TS
) => TypeOfApply<F, DomainOf<(a: A, ...rest: MapAOf<TS>) => any>, L, U>
export function sequenceTv2(F: Apply<unknown>) {
return <TS extends HKT<any, A>[], A>(fa: HKT<any, A>, ...ts: TS) => sequenceT(F)(fa, ...ts)
}
export function call<F extends UnknownApply>(
F: F
): <TS extends TypeOfApply<F, any, L, U>[], A, L, U>(
fa: TypeOfApply<F, A, L, U>,
...ts: TS
) => <B>(f: (a: A, ...rest: MapAOf<TS>) => B) => TypeOfApply<F, B, L, A>
export function call(F: Apply<unknown>) {
return <TS extends HKT<any, any>[], A>(fa: HKT<any, A>, ...ts: TS) => <B>(
f: (a: A, ...rest: any[]) => B
) => F.map(sequenceTv2(F)(fa, ...ts), ([a, ...rest]) => f(a, ...rest))
}
const callIO = call(io)
const callEither = call(either)
const ok = <A>(a: A) => either.of<Error, A>(a)
const ko = <A>(e: Error) => left<Error, A>(e)
const a = callIO(io.of('foo'), io.of(2))(tuple) // IO<[string, number]>
const b = callEither(ok('foo'), ok(2))(tuple) // Either<Error, [string, number]>
const c = callEither(ok('foo'), ko(Error('Failed')))(tuple)
a.chain(log).run() // ['foo', 2]
b.fold(log, log).run() // ['foo', 2]
c.fold(log, log).run() // Error('Failed')
@christianbradley Awesome!
I think I've found a better way - here's an example of rewriting a generic lift for Functor:
import { URIS, URIS2, URIS3, URIS4, Type4, Type3, Type2, Type, HKT } from 'fp-ts/lib/HKT'
import {
Functor1,
Functor2,
Functor2C,
Functor3C,
Functor3,
Functor4,
Functor
} from 'fp-ts/lib/Functor'
import { io } from 'fp-ts/lib/IO'
import { either, left } from 'fp-ts/lib/Either'
export type AnyFunctor<U = any, L = any> =
| { [URI in URIS]: Functor1<URI> }[URIS]
| { [URI in URIS2]: Functor2<URI> | Functor2C<URI, L> }[URIS2]
| { [URI in URIS3]: Functor3<URI> | Functor3C<URI, U, L> }[URIS3]
| { [URI in URIS4]: Functor4<URI> }[URIS4]
| Functor<any>
export type TypeOfFunctor<F extends AnyFunctor, X, U, L, A> = F extends Functor4<infer URI>
? Type4<URI, X, U, L, A>
: F extends Functor3C<infer URI, infer U, infer L>
? Type3<URI, U, L, A>
: F extends Functor3<infer URI>
? Type3<URI, U, L, A>
: F extends Functor2C<infer URI, infer L>
? Type2<URI, L, A>
: F extends Functor2<infer URI>
? Type2<URI, L, A>
: F extends Functor1<infer URI>
? Type<URI, A>
: F extends Functor<infer URI>
? HKT<URI, A>
: HKT<unknown, A>
export interface Lift<F extends AnyFunctor> {
<A, B>(f: (a: A) => B): <X, U, L>(
fa: TypeOfFunctor<F, X, U, L, A>
) => TypeOfFunctor<F, X, U, L, B>
}
export function lift<F extends AnyFunctor>(F: F): Lift<F>
export function lift<URI>(F: Functor<URI>): Lift<Functor<URI>> {
return f => fa => F.map(fa, f)
}
// examples
const length = (s: string) => s.length
const liftIO = lift(io)
const liftEither = lift(either)
const a = liftIO(length)(io.of('foo')) // IO<number>
const b = liftEither(length)(either.of<Error, string>('foo')) // Either<Error, number>
const c = liftEither(length)(left<Error, string>(Error('Failure'))) // Either<Error, number>
a.map(console.log).run() // 3
b.fold(console.error, console.log) // 3
c.fold(console.error, console.log) // Error: Failure
Started a branch in my fork (https://github.com/christianbradley/fp-ts/tree/typeclass-boilerplate) - here's a commit showing type helper types and refactoring of Functor#lift: https://github.com/christianbradley/fp-ts/commit/f27c4037179e431732093276819c12d5b37444cd#diff-42e265124f3b2ebf67776699cb8814d1
The types needed at minimum are TypeOfFunctor, AnyFunctor, and CaseOfFunctor. Same can be said for Apply, etc. This is what #lift looks like:
export type Lifted<F extends AnyFunctor, A, B> = CaseOfFunctor<
F,
<X, U, L>(fa: TypeOfFunctor<F, X, U, L, A>) => TypeOfFunctor<F, X, U, L, B>,
<U, L>(fa: TypeOfFunctor<F, never, U, L, A>) => TypeOfFunctor<F, never, U, L, B>,
<L>(fa: TypeOfFunctor<F, never, never, L, A>) => TypeOfFunctor<F, never, never, L, B>,
(fa: TypeOfFunctor<F, never, never, never, A>) => TypeOfFunctor<F, never, never, never, B>
>
export type Lift<F extends AnyFunctor> = <A, B>(f: (a: A) => B) => Lifted<F, A, B>
export function lift<F extends AnyFunctor>(F: F): Lift<F>
export function lift<URI>(F: Functor<URI>): Lift<Functor<URI>> {
return f => fa => F.map(fa, f)
}
@gcanti - any thoughts? If we can lock down the type names etc I don't mind going ahead and knocking this out and sending a pull request.
Most helpful comment
Here's a rewrite with two updates:
1 - TypeParamsOf avoids reliance upon _A, _URI - uses inference from all known types
2 - By passing required first param, allow inference of L, U, X etc from usage - this is really helpful as the current implementation does not do this, and manually passing the type parameters requires passing the full args type as well
I added sequenceTv2 as an example as well