Effect CommunityEC
Effect Community3y ago
126 replies
WIP

Generic Branded Type Constructors

I would like to have generic branded type constructors like so:
type Foo<T> = T & Brand.Brand<'Foo'>

interface $Foo extends TypeLambda {
    type: Foo<this['Target']>
}

const Foo = Brand.nominal<$Foo>()

const foo = Foo([1]); // Foo<number[]>


At current, the alternative is the following, which does not allow to compose generic branded constructors with Brand.all
const Foo = <A>(a: A) => Brand.nominal<Foo<A>>()(a as any)

// which is better written like so
const Foo = <A>(a: A) => a as Foo<A>


The implementation could look something like this
declare const nominal: {
    <A extends Brand<any>>(): Brand.Constructor<A>
    <A extends TypeLambda>(): Brand.GenericConstructor<A>
};

interface GenericConstructor<in out $A extends TypeLambda> {
        readonly [RefinedConstructorsTypeId]: RefinedConstructorsTypeId;
        <T>(args: T): Kind<$A, never, never, never, T>;
        option: <T>(args: T) => Option.Option<Kind<$A, never, never, never, T>>;
        either: <T>(args: T) => Either.Either<Brand.BrandErrors, Kind<$A, never, never, never, T>>;
        refine: <T>(a: T) => a is T & Kind<$A, never, never, never, T>;
    }

I didn't take the time to look into Brand.all but the idea would be that if there are concrete branded constructors in the arguments list, the generic ones should take the intersection of their base values as input, otherwise the return value is generic.
Was this page helpful?