1. 14

  2. 5

    We have the types Media and MediaElement, which both have id: string properties. These two types were often used together, and it was surprisingly very easy to mistakenly give the media element’s ID when the media’s ID was expected and vice versa.

    Anecdotal evidence against “Why Duck Typing Is Safe” http://www.jerf.org/iri/post/2954

    1. 3

      An id property is not really duck typing since it is implementing an identity of some kind. Just not a useful identity in this case.

    2. 4

      This pattern also came up in opaque types let you think locally, where “opaque types” is the Elm community’s term for nominal types. The pattern of shimming opaque or nominal types into TypeScript with an additional pseudo-private property is also known as “branded” typing.

      Although it’s a clever pattern, if you apply it to a native type like a string or array, you break the rule that you don’t modify objects you don’t own, where ownership is defined as non-native objects that you create yourself. If you don’t believe breaking this rule can come to tears, let me tell you about me from about 12 years ago. I came to be a big fan of a library called Sugar, which was like underscore but added functionality to native objects. Unfortunately, some other libraries in my client’s production environment had the same idea but very different implementations. I still break this rule for polyfills since they’re built into most front-end framework scaffolding, and it still comes back to bite me. Say you have two polyfills, A and B. Polyfill A has a correct implementation of Map and loads first. B has a broken implementation and loads last. Which one wins?

      1. 4

        Some languages let you define “cheap” copies of types that are nevertheless considered separate (that is, not mere synonyms). In Kotlin the feature is called inline types. In Haskell, newtypes (probably not the best name).

        Haskell has some secondary features built around newtypes, like the coercion mechanism and selective derivation of typeclass instances from the base type (or a different newtype over the same base type, which turns out to be quite useful!)

        1. 3

          I was really confused by this article and your comment because I thought TypeScript supported opaque types, but I was thinking of Flow which has native support for them. Whoops!

          This got me wondering why it isn’t supported and it looks like it’s one of the oldest requests in TS: https://github.com/microsoft/TypeScript/issues/202. That thread has a number of linked experiments but none have landed yet.

          1. 2

            This implementation doesn’t modify types it doesn’t own. Their MediaId and MediaElementId types really are strings at runtime.

            1. 2

              Good catch, thanks. I stand corrected. I missed this magic at the end:

              // Use type casting as a hack around having 
              // to instantiate the symbol property
              export const newMediaId = (id: string) => id as MediaId
              export const newMediaElementId = (id: string) => id as MediaElementId
            2. 2

              Swift defines this as “don’t implement conformance to protocols for types where you own neither”. In other words, it is perfectly fine that you conform your own type to ExpressibleByStringLiteral from the standard library or to extend a type from a third party library to MyProtocol but not to conform a third party type to a third party protocol since the next version of that library may implement that itself or that another library does the same.

            3. 3

              Symbol tags are terrific for typescript. I’ve used it extensively in the past especially combined with ts-pattern to getgood pattern matching (generally using a field called t on an interface/class).

              Worth noting that newtype-ts is a semi-popular library for this in the fp-ts adjacent ecosystem.

              1. 2

                Seems similar to some of the stuff DeepKit does to implement TS reflection, though all of this is slightly over my head.