Closed pbadenski closed 4 years ago
import * as array from 'fp-ts/lib/Array'
import { Monad1 } from 'fp-ts/lib/Monad'
import { Option, some, none } from 'fp-ts/lib/Option'
import * as optionT from 'fp-ts/lib/OptionT'
import { pipe, pipeable } from 'fp-ts/lib/pipeable'
// a Monad instance for `Array<Option<A>>`...
declare module 'fp-ts/lib/HKT' {
interface URItoKind<A> {
ArrayOption: Array<Option<A>>
}
}
const arrayOption: Monad1<'ArrayOption'> = {
URI: 'ArrayOption',
...optionT.getOptionM(array.array)
}
// ...getting pipeable functions...
const { ap, apFirst, apSecond, chain, chainFirst, flatten, map } = pipeable(arrayOption)
// ...usage
pipe(
arrayOption.of(1),
map(v => v + 1),
chain(n => (n > 0 ? [some(n + 1), some(n - 1)] : [none]))
)
Thanks! I was worried that was the answer.. Out of curiosity do you think there's TypeScript features coming any time soon that could make it less verbose?
@gcanti Shouldn't we reconsider changing typeclasses signatures to be always data-last? I have exactly the same problem right now trying to write tests for new RemoteDataT
transformer and it's quite weird for tests to pollute the source with globally registered monad instance only to make pipe work
Just played a bit, looks nice:
import { HKT, Kind, URIS } from 'fp-ts/lib/HKT';
import { pipe } from 'fp-ts/lib/pipeable';
///////
export interface Functor<F> {
readonly URI: F;
readonly map: <A, B>(f: (a: A) => B) => (fa: HKT<F, A>) => HKT<F, B>;
}
export interface Functor1<F extends URIS> {
readonly URI: F;
readonly map: <A, B>(f: (a: A) => B) => (fa: Kind<F, A>) => Kind<F, B>;
}
///////
export interface Nothing {
readonly _tag: 'Nothing';
}
export const nothing: Maybe<never> = {
_tag: 'Nothing',
};
export interface Just<A> {
readonly _tag: 'Just';
readonly value: A;
}
export const just = <A>(value: A): Maybe<A> => ({
_tag: 'Just',
value,
});
export type Maybe<A> = Nothing | Just<A>;
declare module 'fp-ts/lib/HKT' {
interface URItoKind<A> {
Maybe: Maybe<A>;
}
}
export const maybe: Functor1<'Maybe'> = {
URI: 'Maybe',
map: f => fa => (fa._tag === 'Just' ? just(f(fa.value)) : nothing),
};
//////////
export type MaybeT<M, A> = HKT<M, Maybe<A>>;
export type MaybeT1<M extends URIS, A> = Kind<M, Maybe<A>>;
export interface MaybeM<M> {
map: <A, B>(f: (a: A) => B) => (ma: MaybeT<M, A>) => MaybeT<M, B>;
}
export interface MaybeM1<M extends URIS> {
map: <A, B>(f: (a: A) => B) => (ma: MaybeT1<M, A>) => MaybeT1<M, A>;
}
export function getMaybeM<M extends URIS>(M: Functor1<M>): MaybeM1<M>;
export function getMaybeM<M>(M: Functor<M>): MaybeM<M>;
export function getMaybeM<M>(M: Functor<M>): MaybeM<M> {
return {
map: f => M.map(maybe.map(f)),
};
}
////////
export type Box<A> = [A];
declare module 'fp-ts/lib/HKT' {
interface URItoKind<A> {
Box: Box<A>;
}
}
export const box: Functor1<'Box'> = {
URI: 'Box',
map: f => fa => [f(fa[0])],
};
//////////
const M = getMaybeM(box);
const inc = (n: number) => n + 1;
const test1: Maybe<number> = pipe(
just(123),
maybe.map(inc),
);
const test2: Box<Maybe<number>> = pipe(
[just(123)],
M.map(inc),
);
How would you make compositions like traverse
pipeable?
import * as O from 'fp-ts/lib/Option';
import * as T from 'fp-ts/lib/Task';
O.option.traverse(T.task);
@OliverJAsh @gcanti already tried https://github.com/gcanti/fp-ts/issues/823#issuecomment-504086813
@OliverJAsh Easily (if typeclasses were curried data-last):
import { HKT, Kind, URIS } from 'fp-ts/lib/HKT';
import { pipe } from 'fp-ts/lib/pipeable';
///////
export interface Functor<F> {
readonly URI: F;
readonly map: <A, B>(f: (a: A) => B) => (fa: HKT<F, A>) => HKT<F, B>;
}
export interface Functor1<F extends URIS> {
readonly URI: F;
readonly map: <A, B>(f: (a: A) => B) => (fa: Kind<F, A>) => Kind<F, B>;
}
export interface Apply<F> extends Functor<F> {
readonly ap: <A, B>(fab: HKT<F, (a: A) => B>) => (fa: HKT<F, A>) => HKT<F, B>;
}
export interface Apply1<F extends URIS> extends Functor1<F> {
readonly ap: <A, B>(fab: Kind<F, (a: A) => B>) => (fa: Kind<F, A>) => Kind<F, B>;
}
export interface Applicative<F> extends Apply<F> {
readonly of: <A>(a: A) => HKT<F, A>;
}
export interface Applicative1<F extends URIS> extends Apply1<F> {
readonly of: <A>(a: A) => Kind<F, A>;
}
export interface Traverse<T> {
<F extends URIS>(F: Applicative1<F>): <A, B>(f: (a: A) => Kind<F, B>) => (ta: HKT<T, A>) => Kind<F, HKT<T, B>>;
<F>(F: Applicative<F>): <A, B>(f: (a: A) => HKT<F, B>) => (ta: HKT<T, A>) => HKT<F, HKT<T, B>>;
}
export interface Traverse1<T extends URIS> {
<F extends URIS>(F: Applicative1<F>): <A, B>(f: (a: A) => Kind<F, B>) => (ta: Kind<T, A>) => Kind<F, Kind<T, B>>;
<F>(F: Applicative<F>): <A, B>(f: (a: A) => HKT<F, B>) => (ta: Kind<T, A>) => HKT<F, Kind<T, B>>;
}
export interface Traversable<T> extends Functor<T> {
readonly traverse: Traverse<T>;
}
export interface Traversable1<T extends URIS> extends Functor1<T> {
readonly traverse: Traverse1<T>;
}
///////
export interface Nothing {
readonly _tag: 'Nothing';
}
export const nothing: Maybe<never> = {
_tag: 'Nothing',
};
export interface Just<A> {
readonly _tag: 'Just';
readonly value: A;
}
export const just = <A>(value: A): Maybe<A> => ({
_tag: 'Just',
value,
});
export type Maybe<A> = Nothing | Just<A>;
declare module 'fp-ts/lib/HKT' {
interface URItoKind<A> {
Maybe: Maybe<A>;
}
}
export const maybe: Applicative1<'Maybe'> & Traversable1<'Maybe'> = {
URI: 'Maybe',
map: f => fa => (fa._tag === 'Just' ? just(f(fa.value)) : nothing),
ap: fab => fa => (fab._tag === 'Nothing' ? nothing : fa._tag === 'Nothing' ? nothing : just(fab.value(fa.value))),
of: just,
traverse: <F>(
F: Applicative<F>,
): (<A, B>(f: (a: A) => HKT<F, B>) => (ta: Maybe<A>) => HKT<F, Maybe<B>>) => f => ta =>
ta._tag === 'Nothing'
? F.of(nothing)
: pipe(
ta.value,
f,
F.map(just),
),
};
////////
export type Box<A> = [A];
declare module 'fp-ts/lib/HKT' {
interface URItoKind<A> {
Box: Box<A>;
}
}
export const box: Applicative1<'Box'> & Traversable1<'Box'> = {
URI: 'Box',
map: f => fa => [f(fa[0])],
ap: fab => fa => [fab[0](fa[0])],
of: a => [a],
traverse: <F>(F: Applicative<F>): (<A, B>(f: (a: A) => HKT<F, B>) => (ta: Box<A>) => HKT<F, Box<B>>) => f => ta =>
pipe(
ta[0],
f,
F.map(v => [v]),
),
};
//////////
const test: Box<Maybe<number>> = pipe(
just(123),
maybe.traverse(box)(n => [n * 2]),
);
const test2: Maybe<Box<number>> = pipe(
[123],
box.traverse(maybe)(n => just(n * 2)),
);
@gcanti Why closing? Do you have any considerations about moving to data-last at least in the next major? Registering any monad transformer or composition factory output in HKT registry only to fit pipeable (which is also incapable of adding any extra helpers outside the lib core) is terrible. And what about pipeable traversable?
tagged as a reminder
I looked in both documentation and issues, but couldn't find any information on this...
Is it possible (right now or planned as a feature) to make composition "pipe-friendly" with a simple syntax?
Currently *Composition signatures cannot be nicely piped without introducing a function wrapper. For example in case of a OptionT monad transformer below: