Open vxgmichel opened 4 years ago
I'd vote for option 2: it's less verbose (no need to keep repeating the pipe.
module name) and familiar to people coming from languages where the builder pattern is common (JS/TS, Rust, Java, Kotlin, ...). The parenthesis issue is alleviated by code editors inserting them automatically. For user defined combinators, maybe that can be solved with inheritance plus a generic entrypoint like Stream.combine
below:
from collections import OrderedDict
from typing import Callable, Concatenate, ParamSpec, TypeVar
from typing_extensions import Self
T = TypeVar("T")
P = ParamSpec("P")
CustomCombinatorT = TypeVar("CustomCombinatorT", bound="Stream")
class Stream:
def combine(
self,
combinator: Callable[Concatenate["Stream", P], CustomCombinatorT],
*args: P.args,
**kwargs: P.kwargs,
) -> CustomCombinatorT:
return combinator(self, *args, **kwargs)
def some_combinator(stream: Stream, arg1: int) -> Stream:
return stream
def invalid_combinator(stream: Stream) -> OrderedDict:
return OrderedDict()
class CustomStream(Stream):
def __init__(self, stream: Stream):
self.stream = stream
def some_custom_method(self, foo: str) -> Self:
return self
def to_custom_stream(stream: Stream) -> CustomStream:
return CustomStream(stream)
xs = (
Stream()
.combine(some_combinator, 10) # OK, returns Stream
.combine(to_custom_stream) # OK, returns CustomStream
.some_custom_method("a") # OK, custom method can now be called directly
.combine(
some_combinator, "foo"
) # doesn't type check, combinator args are type checked
.combine(
invalid_combinator
) # doesn't type check, returned type must inherit from Stream
)
The current pipe
|
syntaxFor the reference, here's how a long pipeline looks once blackified:
The aiostream pipe syntax is often (and legitimately) seen as magical. Conceptually though, it's quite simple: the pipe combinators (in
aiostream.pipe.*
) are just curried and flipped version of the standard combinators (inaiostream.stream.*
). For instance:Moreover, the pipe syntax is simply defined as regular function composition:
Combining those two ideas, we get:
That's neat but we can't really expect to convince every one that uses aiostream that "it's not magical, it's simply curried and flipped combinators with syntactic sugar for function composition".
Another issue is that the pipe operator precedence does not play well with the await statement:
Alternative 1, the
toolz
wayFor the reference, here's how a long pipeline would look once blackified:
Pros:
Cons:
pipe
namespace for flipped combinatorspipe
function has to be added, and the name conflicts with thepipe
namespaceAlternative 2, the rust way
For the reference, here's how a long pipeline would look once blackified (the parentheses have to be added explicitly though):
Pros:
pipe
scope for flipped combinators, methods already do thatCons: