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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Using classes to define Type Class & ADT #982

Closed
6 tasks
ENvironmentSet opened this issue Oct 24, 2019 · 2 comments
Closed
6 tasks

Proposal: Using classes to define Type Class & ADT #982

ENvironmentSet opened this issue Oct 24, 2019 · 2 comments

Comments

@ENvironmentSet
Copy link

ENvironmentSet commented Oct 24, 2019

馃殌 Feature request

Current Behavior

Currently, we define ADT by using interfaces, type synonyms, union type and normal function:

/** 
-- For comparison, I'll use following Haskell type and type class.

class Monoid a where
  mempty :: a
  mappend :: a -> a -> a

data List a = Nil | Cons a (List a)

instance Monoid (List a) where
  mempty = Nil
  mappend Nil ys = ys
  mappend xs Nil = xs
  mappend (Cons x xs) ys = Cons x $ mappend xs ys
**/
import { Kind, URIS } from 'fp-ts/lib/HKT';

interface Monoid1<F extends URIS> {
  getMempty<A>(): Kind<F, A>;
  mappend<A>(x: Kind<F, A>, y: Kind<F, A>): Kind<F, A>;
}

declare module 'fp-ts/lib/HKT' {
  interface URItoKind<A> {
    List: List<A>;
  }
}

const URI: 'List' = 'List';

type URI = typeof URI;

interface Nil {
  type: 'Nil';
}

interface Cons<A> {
  type: 'Cons';
  value: A;
  next: List<A>;
}

type List<A> = Nil | Cons<A>;

function nil(): Nil {
  return { type: 'Nil' };
}

function cons<A>(value: A, next: List<A>): Cons<A> {
  return { type: 'Cons', value, next };
}

const list: Monoid1<URI> = {
  getMempty: nil,
  mappend<A>(xs: List<A>, ys: List<A>): List<A> {
    if (xs.type === 'Cons') return cons(xs.value, list.mappend(xs.next, ys));
    else return ys;
  }
};

Desired Behavior

So redundant. Definitions are spread out over source code.
And, current way of simulating ad-hoc polymorphisms with type class instance is really, really verbose to use(We must give type class instance definition to ad-hoc polymorphic function every time we call them).

Suggested Solution

Why don't we use classes? class combines definitions into one. also, we can easily infer type class instance from value when we use classes for defining ADT.

Here is my solution:

// For simplification, I skipped processes of type-level defunctionalization for type List.
abstract class Monoid<A> { // Type Class Monoid
  public abstract getMempty(): A; // Required operations
  public abstract mappend(x: A, y: A): A;

  public static getMempty<A extends Monoid<A>>(instance: A): Monoid<A> { // Generalized operations
    return instance.getMempty();
  }

  public static mappend<A>(instance: Monoid<A>, x: A, y: A): A; // support for primitive type
  public static mappend<A extends Monoid<A>>(instance: A, y: A): Monoid<A>; // support for reference type
  public static mappend<A, B extends Monoid<B>>(instance: Monoid<A> | B, y: A | B, z?: A): A | Monoid<B> {
    if (typeof z === 'undefined') return (instance as B).mappend(instance as B, y as B);
    else return (instance as Monoid<A>).mappend(y as A, z);
  }
}

abstract class List<A> extends Monoid<List<A>> {
  public getMempty<A>(): Nil<A> {
    return new Nil;
  }

  public mappend(xs: List<A>, ys: List<A>): List<A> {
    if (xs instanceof Cons) return new Cons(xs.value, Monoid.mappend(xs.next, ys));
    return ys;
  }
}

class Nil<A> extends List<A> {}

class Cons<A> extends List<A> {
  public constructor(public readonly value: A, public readonly next: List<A>) {
    super();
  }
}

Who does this impact? Who is this for?

This proposal is intended to help all users that uses this library.

Describe alternatives you've considered

I'll add alternatives tomorrow, I'm so tired now.

Additional context

Actually, this proposal has some issues that I want to have conversation about:

  • how can we simplify function overloading like mappend's one?
  • constructors are not good to compose. Is it good to add accessor property that retrieves normal function form of constructor to classes that express value constructor?
  • how can we use instanceof type guard well? it doesn't work well in some cases.
  • When ad-hoc polymorphic function takes over two value which we may infer type class instance from, how can we determine what we should use?
  • Is it good to add class which abstracts classes that express ADT and TypeClass(furthermore, ADT that supports inference based over its value)?
  • How can we support implementing type class instances? should we use mixin for multiple inheritance?

Your environment

Software Version(s)
TypeScript 3.6.2
@ENvironmentSet ENvironmentSet changed the title Proposal: Using classes to defining Type Class & ADT Proposal: Using classes to define Type Class & ADT Oct 24, 2019
@DenisFrezzato
Copy link
Collaborator

fp-ts@1 had class encoding. In this issue you can see what were the reasons that leaded to the actual encoding.

@ENvironmentSet
Copy link
Author

@DenisFrezzato Thank you. I've checked that thread, and It sounds fair enough to close this issue.

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

No branches or pull requests

2 participants