Branded types are extremely powerful for security-critical systems, like finance apps, which require a lot of validations and checks before actions can be performed.
This part made be break into a sweat.
I know it’s not fashionable to be a JavaScript (TypeScript) hater anymore, but while TypeScript’s type system has a lot of interesting power, it also misses extremely basic static soundness and type safety.
In this specific case with so-called branded types and the advice to use type predicate functions, it’s extremely easy to mess up a predicate function for a non-trivial type. For example, imagine you want to use a branded type for a NonEmptyArray type (which I’ve done). The following type + predicate function is wrong. Can you spot why?
declare const NonEmptyArray: unique symbol
type NonEmptyArray<T> = readonly T[] & {
readonly [NonEmptyArray]: unique symbol
}
const isNonEmptyArray = <T>(arr: readonly T[]): arr is NonEmptyArray<T> => arr.length > 0
The reason it’s wrong is because any mutable array that happens to be non-empty at the moment you call isNonEmptyArray will pass the test and the compiler will treat is as a non-empty array even though it could become empty. Here’s an example:
declare function useNonEmptyArray(arr: NonEmptyArray<number>)
const x = [1] // Not empty
if (isNonEmptyArray(x)) {
x.pop() // x is empty now
// Compiler still thinks x is a NonEmptyArray
useNonEmptyArray(x) // Compiler doesn't complain
}
And this is ignoring that your type predicate could just be wrong by forgetting to recursively test the types of an object type’s fields.
I used branded types extensively in my TypeScript code, but TypeScript honestly provides so little type safety that we need to be really careful to not let it lull us into a false sense of security.
yeah, you can construct the specific pattern OP describes with a combination of Data.Tagged + DataKinds + Symbol.
More concretely:
{-# LANGUAGE DataKinds, RankNTypes, StandaloneKindSignatures, TypeApplications #-}
import Data.Tagged (Tagged (..))
import Data.Kind (Type)
import GHC.TypeLits (Symbol)
type Branded :: Symbol -> Type -> Type
newtype Branded brand t = Tagged brand t
brand :: forall brand t. t -> Branded brand t
brand x = Branded (Tagged x)
You could do without Data.Tagged & just make your own newtype wrappers + helpers, but the library already exists so might as well leverage it & plumb through any helper functions.
Anyone who wants to read this not on Twitter can check out the unrolled thread here: https://threadreaderapp.com/thread/1625173884885401600.html
This part made be break into a sweat.
I know it’s not fashionable to be a JavaScript (TypeScript) hater anymore, but while TypeScript’s type system has a lot of interesting power, it also misses extremely basic static soundness and type safety.
In this specific case with so-called branded types and the advice to use type predicate functions, it’s extremely easy to mess up a predicate function for a non-trivial type. For example, imagine you want to use a branded type for a NonEmptyArray type (which I’ve done). The following type + predicate function is wrong. Can you spot why?
The reason it’s wrong is because any mutable array that happens to be non-empty at the moment you call
isNonEmptyArray
will pass the test and the compiler will treat is as a non-empty array even though it could become empty. Here’s an example:And this is ignoring that your type predicate could just be wrong by forgetting to recursively test the types of an object type’s fields.
I used branded types extensively in my TypeScript code, but TypeScript honestly provides so little type safety that we need to be really careful to not let it lull us into a false sense of security.
This looks a lot like what
newtype
does in Haskell.yeah, you can construct the specific pattern OP describes with a combination of
Data.Tagged
+DataKinds
+Symbol
.More concretely:
…which can be examined in a REPL:
You could do without
Data.Tagged
& just make your ownnewtype
wrappers + helpers, but the library already exists so might as well leverage it & plumb through any helper functions.I am not familiar but did some Googling. In your opinion, is this wiki page a decent overview and explainer?
Yes, definitely.