Hacker News new | ask | show | jobs
by sdeframond 16 days ago
Funny, I ran into the same pattern just a few months ago!

In practice, I found it difficult for coworkers to read and understand so I dropped the idea.

Another limitation I found is that it breaks down when you start using inheritance. For example:

```

class _A: pass

A = NewType("A", _A)

class _B(_A): pass

B = NewType("B", _B)

def foo(a: A) -> None: pass

b = B(_B())

foo(b) # Mypy is not happy: Argument 1 to "foo" has incompatible type "B"; expected "A"

foo(A(b)) # Mypy is OK

```

2 comments

Just use a generic and make it bound to (A, B):

    from typing import *
    
    
    class _A:
        pass
    
    class _B(_A):
        pass
    
    A = NewType("A", _A)
    B = NewType("B", _B)
    
    def foo[T: (A, B)](val: T) -> T:
        return val
    
    a = A(_A())
    b = B(_B())
    
    _a = foo(a)
    _b = foo(b)
    
    reveal_type(_a)
    reveal_type(_b)
Playground here: https://mypy-play.net/?mypy=latest&python=3.12&gist=36573363...
This does seem like an abstraction leak though.
The abstraction gets leaky once you expect the distinct NewTypes to adhere to the original inheritance property. I think that's a wrong assumption from the get-go.

OP could just do:

    def foo(val: _A) -> None:
        pass
...and it'll accept both NewTypes just fine. I guess it depends on whether foo is designed to be public or private.
This works but it does not let other modules define such methods.
(On Hacker News you can do code blocks by indenting each line with two spaces.)
Aaah nice! Thank you!