Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compositions & pipe #925

Closed
pbadenski opened this issue Aug 6, 2019 · 9 comments
Closed

Compositions & pipe #925

pbadenski opened this issue Aug 6, 2019 · 9 comments
Labels

Comments

@pbadenski
Copy link
Contributor

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:

const optionArray = optionT.getOptionM(array.array);

pipe(
  optionArray.of(1),
  m => optionArray.map(m, v => v + 1)
  // how can you make below possible?
  // optionArray.map(v => v + 1)
)
@gcanti
Copy link
Owner

gcanti commented Aug 7, 2019

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]))
)

@pbadenski
Copy link
Contributor Author

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?

@raveclassic
Copy link
Collaborator

raveclassic commented Aug 21, 2019

@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

@raveclassic
Copy link
Collaborator

raveclassic commented Aug 21, 2019

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),
);

@OliverJAsh
Copy link
Collaborator

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);

@mlegenhausen
Copy link
Collaborator

@OliverJAsh @gcanti already tried #823 (comment)

@raveclassic
Copy link
Collaborator

raveclassic commented Oct 14, 2019

@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 gcanti closed this as completed Jan 11, 2020
@raveclassic
Copy link
Collaborator

raveclassic commented Jan 11, 2020

@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?

@gcanti gcanti added this to the v3 milestone Jan 11, 2020
@gcanti
Copy link
Owner

gcanti commented Jan 11, 2020

tagged as a reminder

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants