/*! * Copyright (c) 2017 by The Funfix Project Developers. * Some rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Try, Success, Option, Some, Either, Right, applyMixins, Throwable } from "funfix-core" import { Eval, IO } from "funfix-effect" import { Future } from "funfix-exec" import { HK, registerTypeClassInstance } from "./kinds" import { Monad, MonadError } from "./monad" import { Comonad, CoflatMap } from "./comonad" import { Eq } from "./eq" /** * Alias used for encoding higher-kinded types when implementing * type class instances. */ export type OptionK = HK, A> /** * Type class instances provided by default for `Option`. */ export class OptionInstances implements Monad>, Eq>, CoflatMap> { // tslint:disable-next-line:variable-name private __unit: Option = Some(undefined) eqv(lh: Option, rh: Option): boolean { if (lh === rh) return true if (lh.isEmpty()) return rh.isEmpty() if (rh.isEmpty()) return false return Eq.testEq(lh.get(), rh.get()) } pure(a: A): Option { return Some(a) } unit(): Option { return this.__unit } ap(fa: OptionK, ff: OptionK<(a: A) => B>): Option { return Option.map2(fa as Option, ff as Option<(a: A) => B>, (a, f) => f(a)) } map(fa: OptionK, f: (a: A) => B): Option { return (fa as Option).map(f) } map2(fa: OptionK, fb: OptionK, f: (a: A, b: B) => Z): Option { return Option.map2(fa as Option, fb as Option, f) } product(fa: OptionK, fb: OptionK): Option<[A, B]> { return Option.map2(fa as Option, fb as Option, (a, b) => [a, b] as [A, B]) } flatMap(fa: OptionK, f: (a: A) => OptionK): Option { return (fa as any).flatMap(f) } tailRecM(a: A, f: (a: A) => OptionK>): Option { return Option.tailRecM(a, f as any) as any } coflatMap(fa: OptionK, ff: (a: OptionK) => B): Option { return Some(ff(fa)) } coflatten(fa: OptionK): Option> { return Some(fa as Option) } // Mixed-in followedBy: (fa: OptionK, fb: OptionK) => Option followedByL: (fa: OptionK, fb: () => OptionK) => Option forEffect: (fa: OptionK, fb: OptionK) => Option forEffectL: (fa: OptionK, fb: () => OptionK) => Option static readonly global: OptionInstances = new OptionInstances() } // Mixins the default implementations applyMixins(OptionInstances, [Monad]) // Registering `OptionInstances` as global instances for Option registerTypeClassInstance(Eq)(Option, OptionInstances.global) registerTypeClassInstance(Monad)(Option, OptionInstances.global) registerTypeClassInstance(CoflatMap)(Option, OptionInstances.global) /** * Alias used for encoding higher-kinded types when implementing * type class instances. */ export type TryK = HK, A> /** * Type class instances provided by default for `Option`. */ export class TryInstances implements MonadError, Throwable>, Eq>, CoflatMap> { eqv(lh: Try, rh: Try): boolean { if (lh === rh) return true if (lh.isSuccess()) { if (rh.isFailure()) return false return Eq.testEq(lh.get(), rh.get()) } else { if (rh.isSuccess()) return false return Eq.testEq(lh.failed().get(), rh.failed().get()) } } pure(a: A): Try { return Success(a) } unit(): Try { return Try.unit() } ap(fa: TryK, ff: TryK<(a: A) => B>): Try { return Try.map2(fa as Try, ff as Try<(a: A) => B>, (a, f) => f(a)) } map(fa: TryK, f: (a: A) => B): Try { return (fa as Try).map(f) } map2(fa: TryK, fb: TryK, f: (a: A, b: B) => Z): Try { return Try.map2(fa as Try, fb as Try, f) } product(fa: TryK, fb: TryK): Try<[A, B]> { return Try.map2(fa as Try, fb as Try, (a, b) => [a, b] as [A, B]) } flatMap(fa: TryK, f: (a: A) => TryK): Try { return (fa as any).flatMap(f) } tailRecM(a: A, f: (a: A) => TryK>): Try { return Try.tailRecM(a, f as any) as any } raise(e: any): Try { return Try.failure(e) } attempt(fa: TryK): Try> { return Try.success((fa as Try).fold( e => Either.left(e), Either.right )) } recoverWith(fa: TryK, f: (e: Throwable) => TryK): Try { return (fa as Try).recoverWith(f as ((e: Throwable) => Try)) } recover(fa: TryK, f: (e: Throwable) => A): Try { return (fa as Try).recover(f as ((e: Throwable) => A)) } coflatMap(fa: TryK, ff: (a: TryK) => B): Try { return Success(ff(fa)) } coflatten(fa: TryK): Try> { return Success(fa as Try) } // Mixed-in followedBy: (fa: TryK, fb: TryK) => Try followedByL: (fa: TryK, fb: () => TryK) => Try forEffect: (fa: TryK, fb: TryK) => Try forEffectL: (fa: TryK, fb: () => TryK) => Try static global: TryInstances = new TryInstances() } // Mixins the default implementations applyMixins(TryInstances, [MonadError]) // Registering `TryInstances` as global instances for Try registerTypeClassInstance(Eq)(Try, TryInstances.global) registerTypeClassInstance(MonadError)(Try, TryInstances.global) registerTypeClassInstance(CoflatMap)(Try, TryInstances.global) /** * Alias used for encoding higher-kinded types when implementing * type class instances. */ export type EitherK = HK, R> /** * Type class instances provided by default for `Either`. */ export class EitherInstances implements Monad>, Eq>, CoflatMap> { // tslint:disable-next-line:variable-name private __unit: Either = Right(undefined) eqv(lh: Either, rh: Either): boolean { if (lh === rh) return true if (lh.isRight()) { if (rh.isLeft()) return false return Eq.testEq(lh.get(), rh.get()) } else { if (rh.isRight()) return false return Eq.testEq(lh.swap().get(), rh.swap().get()) } } pure(a: A): Either { return Right(a) } unit(): Either { return this.__unit } ap(fa: EitherK, ff: EitherK B>): Either { const faE = (fa as any) as Either const ffE = (ff as any) as Either B> return Either.map2(faE, ffE, (a, f) => f(a)) } map(fa: EitherK, f: (a: A) => B): Either { return ((fa as any) as Either).map(f) } map2(fa: EitherK, fb: EitherK, f: (a: A, b: B) => Z): Either { return Either.map2((fa as any) as Either, (fb as any) as Either, f) } product(fa: EitherK, fb: EitherK): Either { return Either.map2( (fa as any) as Either, (fb as any) as Either, (a, b) => [a, b] as [A, B]) } flatMap(fa: HK, A>, f: (a: A) => HK, B>): HK, B> { return (fa as any).flatMap(f) } tailRecM(a: A, f: (a: A) => HK, Either>): HK, B> { return Either.tailRecM(a, f as any) as any } coflatMap(fa: EitherK, ff: (a: EitherK) => B): Either { return Right(ff(fa)) } coflatten(fa: EitherK): Either> { return Right(fa as Either) } // Mixed-in followedBy: (fa: EitherK, fb: EitherK) => Either followedByL: (fa: EitherK, fb: () => EitherK) => Either forEffect: (fa: EitherK, fb: EitherK) => Either forEffectL: (fa: EitherK, fb: () => EitherK) => Either static global: EitherInstances = new EitherInstances() } // Mixins the default implementations applyMixins(EitherInstances, [Monad]) // Registering `TryInstances` as global instances for Try registerTypeClassInstance(Eq)(Either, EitherInstances.global) registerTypeClassInstance(Monad)(Either, EitherInstances.global) registerTypeClassInstance(CoflatMap)(Either, EitherInstances.global) /** * Alias used for encoding higher-kinded types when implementing * type class instances. */ export type EvalK = HK, A> /** * Type class instances provided by default for `Eval`. */ export class EvalInstances implements Monad>, Comonad> { pure(a: A): Eval { return Eval.now(a) } flatMap(fa: EvalK, f: (a: A) => EvalK): Eval { return (fa as any).flatMap(f) } tailRecM(a: A, f: (a: A) => EvalK>): Eval { return Eval.tailRecM(a, f as any) as any } ap(fa: EvalK, ff: EvalK<(a: A) => B>): Eval { return (fa as Eval).flatMap(a => (ff as Eval<(a: A) => B>).map(f => f(a)) ) } map(fa: EvalK, f: (a: A) => B): Eval { return (fa as Eval).map(f) } unit(): Eval { return Eval.unit() } coflatMap(fa: EvalK, ff: (a: EvalK) => B): Eval { return Eval.now(ff(fa)) } coflatten(fa: EvalK): Eval> { return Eval.now(fa as Eval) } extract(fa: EvalK): A { return (fa as Eval).get() } // Mixed-in map2: (fa: EvalK, fb: EvalK, f: (a: A, b: B) => Z) => Eval product: (fa: EvalK, fb: EvalK) => EvalK<[A, B]> followedBy: (fa: EvalK, fb: EvalK) => Eval followedByL: (fa: EvalK, fb: () => EvalK) => Eval forEffect: (fa: EvalK, fb: EvalK) => Eval forEffectL: (fa: EvalK, fb: () => EvalK) => Eval static global: EvalInstances = new EvalInstances() } // Mixins the default implementations applyMixins(EvalInstances, [Monad, Comonad]) // Registering `EvalInstances` as global instances for `Eval` registerTypeClassInstance(Monad)(Eval, EvalInstances.global) registerTypeClassInstance(Comonad)(Eval, EvalInstances.global) /** * Alias used for encoding higher-kinded types when implementing * type class instances. */ export type FutureK = HK, A> /** * Type class instances provided by default for `Future`. */ export class FutureInstances implements MonadError, Throwable>, CoflatMap> { pure(a: A): Future { return Future.pure(a) } flatMap(fa: FutureK, f: (a: A) => FutureK): Future { return (fa as any).flatMap(f) } tailRecM(a: A, f: (a: A) => FutureK>): Future { return Future.tailRecM(a, f as any) as any } ap(fa: FutureK, ff: FutureK<(a: A) => B>): Future { return (fa as Future).flatMap(a => (ff as Future<(a: A) => B>).map(f => f(a)) ) } map(fa: FutureK, f: (a: A) => B): Future { return (fa as Future).map(f) } unit(): Future { return Future.unit() } raise(e: Throwable): Future { return Future.raise(e) } attempt(fa: FutureK): Future> { return (fa as Future).attempt() as any } recoverWith(fa: FutureK, f: (e: Throwable) => FutureK): Future { return (fa as Future).recoverWith(f as ((e: any) => Future)) } recover(fa: FutureK, f: (e: Throwable) => A): Future { return (fa as Future).recover(f as ((e: any) => A)) } map2(fa: FutureK, fb: FutureK, f: (a: A, b: B) => Z): Future { return Future.map2(fa as any, fb as any, f as any) as any } coflatMap(fa: FutureK, ff: (a: FutureK) => B): Future { return Future.pure(ff(fa)) } coflatten(fa: FutureK): Future> { return Future.pure(fa as Future) } // Mixed-in product: (fa: FutureK, fb: FutureK) => FutureK<[A, B]> followedBy: (fa: FutureK, fb: FutureK) => Future followedByL: (fa: FutureK, fb: () => FutureK) => Future forEffect: (fa: FutureK, fb: FutureK) => Future forEffectL: (fa: FutureK, fb: () => FutureK) => Future static global: FutureInstances = new FutureInstances() } // Mixins the default implementations applyMixins(FutureInstances, [MonadError, CoflatMap]) // Registering `FutureInstances` as global instances for `Future` registerTypeClassInstance(MonadError)(Future, FutureInstances.global) registerTypeClassInstance(CoflatMap)(Future, FutureInstances.global) /** * Alias used for encoding higher-kinded types when implementing * type class instances. */ export type IOK = HK, A> /** * Type class instances provided by default for `IO`. */ export class IOInstances implements MonadError, Throwable>, CoflatMap> { pure(a: A): IO { return IO.pure(a) } flatMap(fa: IOK, f: (a: A) => IOK): IO { return (fa as any).flatMap(f) } tailRecM(a: A, f: (a: A) => IOK>): IO { return IO.tailRecM(a, f as any) as any } ap(fa: IOK, ff: IOK<(a: A) => B>): IO { return (fa as IO).flatMap(a => (ff as IO<(a: A) => B>).map(f => f(a)) ) } map(fa: IOK, f: (a: A) => B): IO { return (fa as IO).map(f) } unit(): IO { return IO.unit() } raise(e: Throwable): IO { return IO.raise(e) } attempt(fa: IOK): IO> { return (fa as IO).attempt() as any } recoverWith(fa: IOK, f: (e: Throwable) => IOK): IO { return (fa as IO).recoverWith(f as ((e: any) => IO)) } recover(fa: IOK, f: (e: Throwable) => A): IO { return (fa as IO).recover(f as ((e: any) => A)) } map2(fa: IOK, fb: IOK, f: (a: A, b: B) => Z): IO { return IO.map2(fa as any, fb as any, f as any) as any } followedBy(fa: IOK, fb: IOK): IO { return (fa as any).followedBy(fb) } followedByL(fa: IOK, fb: () => IOK): IO { return (fa as any).followedBy(IO.suspend(fb as any)) } forEffect(fa: IOK, fb: IOK): IO { return (fa as any).forEffect(fb) } forEffectL(fa: IOK, fb: () => IOK): IO { return (fa as any).forEffect(IO.suspend(fb as any)) } product(fa: IOK, fb: IOK): IO<[A, B]> { return IO.map2(fa as any, fb as any, (a, b) => [a, b]) as any } coflatMap(fa: IOK, ff: (a: IOK) => B): IO { return IO.pure(ff(fa)) } coflatten(fa: IOK): IO> { return IO.pure(fa as IO) } static global: IOInstances = new IOInstances() } // Mixins the default implementations applyMixins(IOInstances, [MonadError, CoflatMap]) // Registering `IOInstances` as global instances for `IO` registerTypeClassInstance(MonadError)(IO, IOInstances.global) registerTypeClassInstance(CoflatMap)(IO, IOInstances.global)