1. 3
    1. 3

      Anyone who wants to read this not on Twitter can check out the unrolled thread here: https://threadreaderapp.com/thread/1625173884885401600.html

    2. 3

      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.

    3. 2

      This looks a lot like what newtype does in Haskell.

      1. 1

        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)
        

        …which can be examined in a REPL:

        > :t brand @"password" "hunter2"
        brand @"password" "hunter2" :: Branded "password" String
        

        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.

      2. 1

        I am not familiar but did some Googling. In your opinion, is this wiki page a decent overview and explainer?

        1. 1

          Yes, definitely.