Type constraints for type class instances? #1887
-
Hi all, I have a type constructor However, the type constraint on Is there a way to make (I am very new to fp-ts; my apologies if I missed something obvious) To illustrate: type Tag = string
type NoTag = never
const notag : NoTag = "" as NoTag
type Add<L extends Tag, R extends Tag> = L | R
function add<L extends Tag, R extends Tag>( l : L, r : R) : Add<L,R> {
return (Math.random() > 0.5) ? l : r
}
type Tagged<T extends Tag, V extends any> = [T, V]
function tag<N extends Tag, T extends Tag, V> ( n : N, tv : Tagged<T, V>) : Tagged<Add<N,T>, V> {
const [t, v] = tv
return [add(n,t), v]
}
// We can define operations on "Tagged"
// which you expect to see in a
// Functor, Applicative, and Monad.
function of<V>( v : V ) : Tagged<NoTag, V> {
return [notag, v]
}
function chain<
L extends Tag,
R extends Tag,
A,B
>(
ta : Tagged<L,A> ,
fatb : (a:A) => Tagged<R,B>
) :
Tagged<Add<L,R>,B>
{
const [l,a] = ta
const [r,b] = fatb(a)
return [add(l,r),b]
}
function ap<
L extends Tag,
R extends Tag,
A,B,
> (
tfab : Tagged<L,(a:A)=>B>,
ta : Tagged<R,A>
) :
Tagged<Add<L,R>, B>
{
const r = chain(ta, (a:A) => chain(tfab, (fab:(A)=>B) => of( fab(a))) )
return r
}
function map<
T extends Tag,
A,B
>(
ta : Tagged<T,A>,
fab : (a:A) => B
) :
Tagged<T,B>
{
const r = ap( of(fab), ta)
return r
}
// But when we try to make Tagged
// an instance of Functor,
// things go wrong.
//https://dev.to/gcanti/getting-started-with-fp-ts-functor-36ek
import { Functor2 } from "fp-ts/Functor"
const URI = "Tagged"
type URI = typeof URI
declare module "fp-ts/HKT"{
interface URItoKind2<E extends Tag, A> { // "extends Tag" added to avoid a different error
Tagged: Tagged<E, A>
}
}
// my map not assignable to typeclass-map;
// types of parameters ta and fa incompatible
// Tagged<E,A> not assignable to Tagged<string,A>
// E not assignable to string
const functor: Functor2<URI> = {
URI,// @ts-expect-error see above
map
}
// if I keep this ts-expect-error, then I get a
// usable functor instance. however, I get
// type-level nonsense.
const v = tag("Num", of(5)) // Tagged<"Num", number>
const a = map( v, (x:number) => x+1 ) // Tagged<"Num", number>
const b = functor.map( v, (x:number) => x+1 ) // Tagged<"Num", number>
const c = functor.map( [42,5], (x:number) => x+1 ) // Tagged<number,number> - nonsense |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
Unfortunately no, there's a workaround when you can fix the second type parameter (see for example const getFunctor = <T extends Tag>(): Functor2C<URI, T> => ({
URI,
_E: undefined as any,
map
})
const functor = getFunctor<"Num">()
const v = tag("Num", of(5)) // Tagged<"Num", number>
const a = map(v, (x: number) => x + 1) // Tagged<"Num", number>
const b = functor.map(v, (x: number) => x + 1) // Tagged<"Num", number>
const c = functor.map([42, 5], (x: number) => x + 1) // error but is admittedly ugly |
Beta Was this translation helpful? Give feedback.
Unfortunately no, there's a workaround when you can fix the second type parameter (see for example
getApplicativeValidation
in theEither
module), i.e.but is admittedly ugly