TypeScript can add types without any binding codes. So, we can support TypeScript just to write type definition codes.
TypeScript manage definitions on main definitions repository, DefinitelyTyped.
Already, some definitions of library supporting fantasy land (parsimmon, tsmonad, etc.) were added.
However, the types of each library were defined by some contributors and has some differences...
I would like you to define TypeScript type signatures of fantasy land for TypeScript and fantasy land user.
That has good influences for us.
Thanks!
If you can type Functor in TypeScript, then go for it. Last I checked, you couldn't.
@joneshf
Actually, I usually use types of TsMonad now.
And, we can use these.
Functor:
https://github.com/cbowdon/TsMonad/blob/master/monad.ts#L97
Oh... Apply type cannot write in TypeScript...
This is illegal typescript code, but I wanted.
interface Apply<(t: T) => U> {
ap(u: U): U;
}
interface Apply<T> {
}
However, Apply type is very strange for me. I think all Apply data must have ap method. Functor type ( map :: Functor f => f a -> (a -> b) -> f b ) is good for me.
If we will support typescript for fanrasy-land, we change Apply type to:
a.ap(f); // a :: A<T>, f :: A<(T => U)>, a.ap(f) :: A<U>
or
A.ap(a, f); // a :: A<T>, f :: A<T => U>, A.ap(a, f) :: A<U>
In this cases, the typescript types are:
interface Apply<T> {
ap<U>(f: Apply<(t: T) => U>): U;
}
or
interface Apply<T> {
ap(a: T, f: Apply<(t: T) => U>): Apply<U>;
}
interface ApplyStatic<T, U> {
new(...args: any[]): Apply<any>;
ap(a: Apply<T>, f: Apply<(t: T) => U>): Apply<U>;
}
Maybe and Either of TsMonad are not supported Apply and Applicative. The reason may be this problem...
How do you do a natural transformation in typescript as well or high order
types. I think you'll hit road blocks a lot and I wonder if it would be
worth it, esp. when you work with free monads and coyoneda.
On Wed, 11 May 2016, 20:31 Mizunashi Mana, [email protected] wrote:
Oh... Apply type cannot write in TypeScript...
This is illegal typescript code, but I wanted.
interface Apply<(t: T) => U> {
ap(u: U): U;
}
interface Apply{
}However, Apply type is very strange for me. I think all Apply data must
have ap method.
If we will support typescript for fanrasy-land, we change Apply type to:a.ap(f): // a :: A
, f :: A<(T => U)>, a.ap(f) :: A or
A.ap(a, f); // a :: A
, f :: A , A.ap(a, f) :: A In this cases, the typescript types are:
interface Apply
{
ap(f: Apply<(t: T) => U>): U;
}or
interface Apply
{
ap(a: T, f: Apply<(t: T) => U>): Apply;
}
interface ApplyStatic{
new(...args: any[]): Apply;
ap(a: Apply, f: Apply<(t: T) => U>): Apply;
}Maybe and Either of TsMonad are not supported Apply and Applicative. The
reason may be this problem...—
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub
https://github.com/fantasyland/fantasy-land/issues/140#issuecomment-218564820
I understood we cannot natural transforming in typescript. TypeScript has some problems on type system. Yoneda type can write on TypeScript because this type is only running. But, CoYoneda cannot.
So, we cannot provide full support for TypeScript.
However, I think we can provide main support (Monad/Monoid/Traversal).
This may be also helpful for JavaScripter.
In TypeScript:
const m = (Functor<A>)Identity(new A());
m.equals(a => Identity(new A()); // this will be compile error
But, this will be ok:
interface Functor<T> {
map<U>(f: (t: T) => U): Functor<U>;
}
interface Eq<T> {
equals(t: T): boolean;
}
class A<T> implements Functor<T>, Eq<A<T>> {
map<U>(f: (t: T) => U): A<U> { return new A(); }
equals(a: A<T>): boolean { return true; }
}
new A().map(a => a).equals(new A());
So, that cases, we cannot:
function fmap<T, U>(f: (t: T) => U): (g: Functor<T>) => Functor<U> {
return a => a.map(f);
}
fmap(a => a)(new A()).equals(new A()); // compile error
In this cases, TypeScript user force to cast types...
You've just shown why the typescript support is pointless from my point of view, by all the notes and changes you've suggested that are required.
I just think the typescript system is too restrictive, by not being free enough (oxymoron considering we're talking about JavaScript).
:-1: from me
Ok, I considered deeply, Apply design is well and to change this design effects some wrong for javascript fantasy land user. And, typescript support is not well for this project.
I'm sorry for taking your time 🙇 .
I'm sorry for taking your time
Please note, the issue wasn't about your proposal, but about the fact that TypeScript is limited by its very nature. Therefore the gains/rewards that we get from it are limited and may even restrict the future additions to the specification.
Having said all of this, there isn't anything stopping you from making the repo hosted somewhere else though, if you do get a benefit from having the TypeScript definitions.
@SimonRichardson
I see.
However, I cannot define Apply type on TypeScript. Adding TypeScript definitions limit APIs for libraries supporting fantasy-land, and I think that disadvantages are greater than advantages providing standardization.
So, I stop declaring TypeScript types.
Thanks.
@joneshf
Actually, I usually use types of TsMonad now.
And, we can use these.Functor:
https://github.com/cbowdon/TsMonad/blob/master/monad.ts#L97
Tell me if I'm wrong here (since I don't know typescript that in-depth), but that doesn't seem to describe Functor properly. It allows the implementer to specify which values they want to work with. Functors don't get to make that choice. Given that interface could we not change this line: https://github.com/cbowdon/TsMonad/blob/f359e2d772a7d6f5d21eba3ba4db85be5a1bd83b/maybe.ts#L54 to the following (and the subsequence implementation) and still have everything typecheck?
export class Maybe<T> implements Monad<T>, Functor<boolean>, Eq<Maybe<T>> {
@joneshf
I understood your talking about. TsMonad's interface is wrong. Sorry.
However,
class Maybe<T> {
fmap<U>(f: (t: T) => U): Maybe<U> { ... }
}
this one can be Functor, cannot? The TsMonad Functor interface can be a helper for declare type.
That will be compile error.
class Maybe<T> implements Functor<boolean> {
fmap<U>(f: (t: T) => U): Maybe<U> { ... }
/* fmap(f: (t: boolean) => boolean): Maybe<T> { ... } // can define */
/* fmap<U>(f: (t: boolean) => U): Maybe<U> { ... } // also can define... */
}
Actually, I'm usual using PureScript, so, not deep TypeScript user. So, maybe, there are some solutions I don't know.
Sorry, I wasn't very clear.
The last example is what I meant.
class Maybe<T> implements Functor<boolean> {
fmap<U>(f: (t: boolean) => U): Maybe<U> { ... }
}
That's not a Functor.
But you're right, the first example is a Functor. This one:
class Maybe<T> {
fmap<U>(f: (t: T) => U): Maybe<U> { ... }
}
You can define them inline to a class, you just can't abstract out the interface with typescript.
@joneshf
I see. That is exactly right. Such code has problems.
function void<T extends Functor<U>, U, R extends Functor<{}>>(fa: T): R {
return fa.fmap(a => ({}));
}
That type signature passed that:
function void<T extends Functor<U>, U, R extends Functor<{}>>(fa: T): R {
return Maybe.Just({});
}
I understood that TsMonad's Functor interface is wrong for that cases.
Thanks.
I tried to make an attempt at this (not having looked at TsMonad) with my limited understanding of these types, and within the limitations of TS interfaces (no static methods, no multiple extending).
I must say I think TS has been making progress; if it isn't there yet, hopefully it'll still get there.
interface Setoid<T> {
equals(b: Setoid<T>): boolean;
}
interface Semigroup<T> {
concat(b: Semigroup<T>): Semigroup<T>;
}
interface Monoid<T> extends Semigroup<T> {
/* static */ empty<T>(): Monoid<T>;
}
interface Functor<T> {
map<U>(fn: (t: T) => U): Functor<U>;
}
interface Apply<T> extends Functor<T> {
apply<U>(fn: Apply<(t: T) => U>): Apply<U>;
}
interface Applicative<T> extends Apply<T> {
/* static */ of<U>(a: U): Applicative<U>;
}
interface Alt<T> extends Functor<T> {
alt(b: T): Alt<T>;
}
interface Plus<T> extends Alt<T> {
/* static */ zero<T>(): Plus<T>;
}
interface Alternative<T> extends Plus<T>, Applicative<T> {
}
interface Foldable<T> {
reduce<U>(fn: (u: U, t: T) => U, u: U): U;
}
interface Traversable<T> extends Functor<T>, Foldable<T> {
traverse<U, V>(fn: (t: T) => Applicative<U>, of: (v: V) => Applicative<V>): Applicative<Traversable<U>>;
}
interface Chain<T> extends Apply<T> {
chain<U>(fn: (t: T) => Chain<U>): Chain<U>;
}
interface ChainRec<T> extends Chain<T> {
/* static */ chainRec<A,B,C>(f: (next: (a: A) => C, done: (b: B) => C, value: A) => ChainRec<C>, i: A): ChainRec<B>;
}
interface Monad<T> extends Applicative<T>, Chain<T> {
}
interface Extend<T> {
extend<U>(f: (v: Extend<T>) => U): Extend<U>;
}
interface Comonad<T> extends Functor<T>, Extend<T> {
extract<U>(): U; // 'same U as in extend's f -- how to bind?
}
interface Bifunctor<T,U> extends Functor<T> /*, Functor<U>*/ {
bimap<B,D>(f: (v: T) => B, g: (v: U) => D): Bifunctor<B,D>;
}
interface Profunctor<T,U> extends Functor<T> /*, Functor<U>*/ {
promap<B,D>(f: (v: T) => B, g: (v: U) => D): Profunctor<B,D>;
}
I've likely made mistakes, but I hope this might be useful.
@tycho01 The problem with these is that some of them actually don't work. I've tried similar interface definitions and found that they're not as good as they seem.
Consider your Functor interface:
interface Functor<T> {
map<U>(fn: (t: T) => U): Functor<U>;
}
This says that map can return _any_ functor. This means that after calling map on a Functor the _only_ thing you know about the return value is that it is _some_ Functor. I.e. you only know that it has a map method. But that is useless—so you'll have to cast it.
We can't write a proper Functor interface since TypeScript lacks higher kinded types. I have run into similair problems in my library Jabz. The best solution I've come of with is to create a map function of the following type: map(f: (t: T) => U, Functor<T>): any. Where Functor is similar to your definition. The any prevents it from being typesafe. But I can find no better solution since the alternative is having a map that requires casting after being called. That is less convenient and just a unsafe.
@paldepind: If I understand correctly, the point here is that even if you have a Maybe implement such an interface, the existing Functor<U> return type would not give enough information to tell you that on a Maybe it should return a Maybe, correct?
It seems that TypeScript recently made a change that feels sort of similar to this ("Use returned values from super calls as 'this'"), though I suppose it's a different context (constructors).
I tried to see if I could make a quick reproduction of the issue you were explaining about, presuming I understand it correctly. I'm trying the following:
interface Functor<T> {
map<U>(fn: (t: T) => U): Functor<U>;
}
class MyFunctor<T> implements Functor<T> {
constructor(public val: T) {};
map<U>(fn: (t: T) => U): MyFunctor<U> {
return new MyFunctor(fn(this.val));
}
}
let strFtor = new MyFunctor('hi');
let numFtor = strFtor.map(s => s.length);
// ^ inferred to be a MyFunctor<number>, as expected
Would you mind explaining a bit further?
There could be a possibility in using a different encoding for HKT as seen in Java or similar. I did try and encode this sort of thing into Haxe, but I ended up having to cast at some point because of the issues with the type system.
I've not used typescript so don't know to what extent any of this is possible.
@tycho01 how does it work for chain, considering you don't return new MyFunctor directly, but from fn.
@SimonRichardson: Thank you; with a similar attempt at Chain I managed to reproduce your reported issues. I attempted a similar method signature of chain<U>(fn: (t: T) => MyChain<U>): MyChain<U>, but on an attempt to implement this, things break down; fn's return type of MyChain<U> is quickly found to be incompatible with the asked return type of Chain<U>, stating every possible difference between them (essentially things the MyChain implementation has extra). I'd hoped TypeScript defaulted to covariance here and would be automatically able to deal with it, but it appears that isn't the case.
I'm not super familiar with how workarounds might work here, but it appears nominal typing is on the TS roadmap. :)
@mizunashi-mana, I think the problem in the interface Apply<(t: T) => U> notation was that you should put generics in there by name, e.g. interface Apply<T, U, V extends (t: T) => U>. That said, my Apply implementation ended up a bit simpler there. :P
@SimonRichardson: I guess you were already on the right path here, but I've been corrected in that thread on needing support for HKT rather than nominal typing.
@tycho01 It's actually support for higher-kinded polymorphism. Typescript already has HKTs (i.e. generics)
@rjmk Typescript doesn't have HKTs, i.e. you can't have a type parametrized by a parametrized type. There is an open issue tracking this.
@masaeedu I was drawing a distinction between type constructors/parameterised types (functions from types to either a type or a type constructor) vs functions from type constructors to types/type constructors. Since I made that comment, I've discovered not everyone makes the distinction with that terminology
I think that I may have figured out how to encode Functor. I avoided the problem of overly general return types by using a string literal type tag. I avoided the lack of covariance that @tycho01 ran into by using adding value: any to the Functor interface to hold the data.
interface Functor<Tag extends string, T> {
tag: Tag; // A tag unique to the functor
value: any; // Holds the actual data.
fmap<U>(f: (x: T) => U): Functor<Tag, U>;
}
class ArrayWrapper<T> implements Functor<"ArrayWrapper", T> {
tag: "ArrayWrapper";
readonly value: Array<T>;
constructor(elements: Array<T>) {
this.value = elements;
}
fmap<U>(f: (x: T) => U): ArrayWrapper<U> {
return new ArrayWrapper<U>(this.value.map(f));
}
}
class Maybe<T> implements Functor<"Maybe", T> {
tag: "Maybe";
readonly value: T | null;
constructor(newValue: T | null) {
this.value = newValue;
}
fmap<U>(f: (x: T) => U): Maybe<U> {
return this.value === null
? new Maybe<U>(null)
: new Maybe(f(this.value));
}
}
function f<Tag extends string, T>(x: Functor<Tag, T>) {
return x.fmap((x: T) => true);
}
const x = new ArrayWrapper([1]);
// This typechecks.
const doesTypecheck: ArrayWrapper<boolean> = f(x)
// You can't assign functors to other functors, even when they share parameters.
const doesNotTypecheck: Maybe<boolean> = f(x);
@TJSomething: Interesting! Comments:
value as any, I wonder if there might be a point in capturing its type in an extra generic in Functor, so the Functor type could tell just a little more about what kind of instance we'd be dealing with. I've no idea if adding this info would be deemed out of scope in the ADT definitions. Either way, you've already demonstrated this isn't needed for it to function.ArrayWrapper: we still wouldn't have it count the built-in types :(, though having a properly-typed Maybe already seems like a big win.fmap, I thought that should be map, while the flat variant would be the one added by Monad?If we would be able to properly type the ADTs (I haven't retried Chain), I think that could make for a follow-up question for fantasy-land: might it make sense to at that point make interface typings (TS and/or Flow) part of this specification/library?
I'd be inclined to think this could be beneficial for the standardization process: if you type your library implementing some ADT instances, and it type-checks with the interfaces, you're probably in proper compliance with the spec.
@TJSomething I used the same technique in fp-ts, though after the 2.4.1 release I'm afraid I have to rewrite the lib from the ground up https://github.com/gcanti/fp-ts/issues/138#issuecomment-311988906
Most helpful comment
Please note, the issue wasn't about your proposal, but about the fact that TypeScript is limited by its very nature. Therefore the gains/rewards that we get from it are limited and may even restrict the future additions to the specification.
Having said all of this, there isn't anything stopping you from making the repo hosted somewhere else though, if you do get a benefit from having the TypeScript definitions.