This is pretty close to type branding (newtype wrapping for the Haskell-inclined), though using template literal types is pretty novel. Normal brands look something like this:
type Brand<BaseType, Brand> = BaseType & { readonly __brand__: Brand };
type FooId = Brand<string, 'FooId'>;
function fooBar(asdf: FooId | 'foobar'): void { }
fooBar will only accept the literal string 'foobar' or a true FooId, but not any arbitrary string. FooId would then come from a function that validates strings as FooIds, or some other part of the app that is an authoritative source for them. Brands extend their BaseType so they can be used anywhere their BaseType is used, but not the inverse
It opens up a huge toolbox of tools that work with Functors and Applicative to make parsing easier, and proves that using those tools will work predictably.
I've used Lenses as a beginner/intermediate haskeller and they're very easy to use. It's easy to get bogged down in the theory and specifics but Lenses are perfectly usable on the practical level without ever diving in too deep. That said, now that RecordDotSyntax is a thing, there's less of a use case for surface level lenses.
fooBar will only accept the literal string 'foobar' or a true FooId, but not any arbitrary string. FooId would then come from a function that validates strings as FooIds, or some other part of the app that is an authoritative source for them. Brands extend their BaseType so they can be used anywhere their BaseType is used, but not the inverse