3

I have a ValuePropagator:

V = TypeVar("V")

class ValuePropagator(ABC, Generic[V]):
    @abstractmethod
    def get(self, funcs: Sequence[Callable[[V], V]], value: V) -> V:
        pass

A sample implementation propagates value eagerly, calling all of funcs on each invocation of get:

class EagerValuePropagator(ValuePropagator[V], Generic[V]):
    def get(self, funcs: Sequence[Callable[[V], V]], value: V) -> V:
        for func in funcs:
            value = func(value)
            
        return value

I also have other implementations that do various levels of caching. One caches the final value after applying all funcs, the other caches intermediate value after applying each func. All is good so far.

My new use case requires intermediate types to be different than V. For example, I may receive 2 funcs: a Callable[[V1], V2] and Callable[[V2], V3]. My get method would then need to return V3, given a V1.

My first effort was to compose these funcs beforehand into one Callable[[V1], V3]. But many implementations of ValuePropagator require access to the separated funcs for caching strategies, and passing a composed Callable would take away this granularity.

How can I change ValuePropagator to support multiple types between these funcs, while keeping type safety? Couple specifications:

  • Passing Callable[[V1], V2] and Callable[[V1], V3] would need to fail type checking because the output of the first callable is of a different type than the input of the second, causing the piping to fail.

  • I need to be able to receive all the functions up front such that parts of my cumulative operation can remain cached, while others remain uncached.

As a last resort, I can keep this logic untyped using Any, but would appreciate any ideas to try beforehand.

Mario Ishac
  • 5,060
  • 3
  • 21
  • 52
  • 1
    My experience has been that Python's type system is not completely mature. It's been designed to handle the most common cases, but fails miserably when having to deal with anything beyond the most simple functions-as-arguments or functions-as-return-values. I will follow this thread in case someone has better answers. – Frank Yellin Nov 12 '20 at 01:17
  • Switching to Haskell-like syntax for brevity, I'm not sure how you would statically specify the type of `[f, g, h]` such that `f . g . h :: a -> d` is defined. `f :: c -> d` and `h :: a -> b` don't have anything in common, other than the existence of `g :: b -> c` in the list to act as the link between them. You need to consider not just the type(s) of functions, but the order in which they appear. – chepner Nov 12 '20 at 01:36
  • That's a [thrist](https://github.com/ggreif/omega/blob/master/doc/Thrist-draft-2011-11-20.pdf). I guess it makes Python's mind explode. – phipsgabler Nov 13 '20 at 08:26

0 Answers0