Skip to content

Generate a nicer NonEmpty for TypeScript #60

Description

@ekilah

Is there appetite for changing the NonEmpty instance?

It currently generates the same way as any other list (e.g. NonEmpty Text and [Text] both generate to string[]):

instance {-# OVERLAPPABLE #-} (TypeScript a) => TypeScript [a] where
getTypeScriptType _ = (getTypeScriptType (Proxy :: Proxy a)) ++ "[]"
getParentTypes _ = [TSType (Proxy :: Proxy a)]
instance (TypeScript a) => TypeScript (NonEmpty a) where
getTypeScriptType _ = getTypeScriptType (Proxy :: Proxy [a])
getParentTypes _ = [TSType (Proxy :: Proxy a)]

In TypeScript, NonEmpty Text can actually be represented natively, like so:

// (not a built-in type)
type NonEmpty<T> = [T, ...T[]]

and then used like this:

interface Foo {
    nonEmptyList: [string, ...string[]]

    orAnotherWay: NonEmpty<string>
}

These fancier tuple types are compatible in all of the important ways in TS (e.g. you can use a [T, ...T] in a position expecting T[]), so this wouldn't really be a breaking change, but it would indeed change people's generated outputs in a way people might not be super familiar with (or care about), so I'm sympathetic to any caution you might express here.


Our typescript client actually represents this NonEmpty<T> in with its own little named type, so another option that might (?) work for me personally would be to mark your instance as OVERLAPPABLE (docs).

That said, I am not very practiced in Haskell to know how likely you are to do that, or if there are downsides, etc. I see it's already there for the regular, not-NonEmpty list type, so maybe this isn't weird?

Supposedly, that'd make it so users of this library could write their own, fancier instance as desired, without a "breaking" sort of change like the above could be seen as.

As-is, I tried this:

instance {-# OVERLAPPING #-} TypeScript a => TypeScript (NE.NonEmpty a) where
  getTypeScriptType _ = "NEList<" <> getTypeScriptType (Proxy @a) <> ">"
  getParentTypes _ = TSType (Proxy @NEList) : getParentTypes (Proxy @a)

but Haskell seems to not like that my instance and the one from this library have the same specificity, or something.

edit: Claude suggests that OVERLAPPABLE would not solve my problem because of the specificity issue.

(Again, not really much of a Haskeller, so apologies if there are some misunderstandings here)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions