LaVOZs

The World’s Largest Online Community for Developers

'; typeclass - Implement a type in Python, for functions in a module (not class)? - LavOzs.Com

def f(x, y):
    return x & 1 == 0 and y > 0

g = lambda x, y: x & 1 == 0 and y > 0

Now the same thing in Haskell:

import Data.Bits

f :: Int -> Int -> Bool
f x y = (.&.) x 1 == 0 && y > 0

That works, however this doesn't:

g = \x y -> (.&.) x 1 == 0 && y > 0

Here's the error this gives:

someFunc :: IO ()
someFunc = putStrLn $ "f 5 7: " ++ ( show $ f 5 7 ) ++ "\tg 5 7: " ++ ( show $ g 5 7 )

• Ambiguous type variable ‘a0’ arising from the literal ‘1’
  prevents the constraint ‘(Num a0)’ from being solved.
  Relevant bindings include
    x :: a0 (bound at src/Lib.hs:13:6)
    g :: a0 -> Integer -> Bool (bound at src/Lib.hs:13:1)
  Probable fix: use a type annotation to specify what ‘a0’ should be.
  These potential instances exist:
    instance Num Integer -- Defined in ‘GHC.Num’
    instance Num Double -- Defined in ‘GHC.Float’
    instance Num Float -- Defined in ‘GHC.Float’
    ...plus two others
    ...plus one instance involving out-of-scope types
    (use -fprint-potential-instances to see them all)
• In the second argument of ‘(.&.)’, namely ‘1’
  In the first argument of ‘(==)’, namely ‘(.&.) x 1’
  In the first argument of ‘(&&)’, namely ‘(.&.) x 1 == 0’
   |
13 | g = \x y -> (.&.) x 1 == 0 && y > 0
   |                     ^

How do I get the same error in Python? - How do I get errors when the input doesn't match expectations?

To be specific, how do I say that a function/lambda MUST have:

  • arity of 5
  • each argument must be numerical
  • each argument must implement __matmul__ (@)
  • return bool

I know that I can roughly do this with: (docstrings and/or PEP484) with abc; for classes. But what can I do for 'loose' functions in a module?

Generally you have three possible approaches:

  1. Let the runtime produce errors for incompatible operations (e.g. 1 + 'foo' is a TypeError).
  2. Do explicit runtime checks for certain attributes; though this is usually discouraged since Python uses duck typing a lot.
  3. Use type annotations and a static type checker.

Your specific points:

  • arity of 5

Define five parameters:

def f(a, b, c, d, e): ...
  • each argument must be numerical

Either static type annotations:

def f(a: int, b: int, c: int, d: int, e: int): ...

And/or runtime checks:

def f(a, b, c, d, e):
    assert all(isinstance(i, int) for i in (a, b, c, d, e))

def f(a, b, c, d, e):
    if not all(isinstance(i, int) for i in (a, b, c, d, e)):
        raise TypeError

asserts are for debugging purposes and can be disabled, an explicit if..raise cannot. Given the verboseness of this and the duck typing philosophy, these approaches are not very pythonic.

  • each argument must implement __matmul__ (@)

The most practical way is probably to let the runtime raise an error intrinsically if the passed values do not support the operation, i.e. just do:

def f(a, b):
    return a @ b  # TypeError: unsupported operand type(s) for @: ... and ...

If you want static type checking for this, you can use a typing.Protocol:

from typing import Protocol

class MatMullable(Protocol):
    def __matmul__(self, other) -> int:
        pass

def f(a: MatMullable, ...): ...

In practice you probably want to combine this with your previous "each argument must be numerical" and type hint for a type that fulfils both these requirements.

  • return bool
def f(...) -> bool: ...

Especially given that the @ operator is mostly used by 3rd party packages like numpy, in practice the most pythonic implementation of such a function is probably something along these lines:

import numpy as np
from numpy import ndarray

def f(a: ndarray, b: ndarray, c: ndarray, d: ndarray, e: ndarray) -> bool:
    return np.linalg.det(a @ b @ c @ d @ e) > 0  # or whatever

You can't directly translate the same typing expectations from Haskell to Python. Haskell is an insanely strongly typed language, while Python is almost the complete opposite.


To type hint a higher order function that accepts such a function as argument, use typing.Callable:

from typing import Callable

def hof(f: Callable[[ndarray, ndarray, ndarray, ndarray, ndarray], bool]): ...
Related