Giter VIP home page Giter VIP logo

expression's Introduction

Hi there ๐Ÿ‘‹

  • ๐Ÿ”ญ Iโ€™m currently working on Fable Python and RxPY
  • ๐ŸŒฑ Iโ€™m currently learning Kafka and Dapr ...
  • ๐Ÿ‘ฏ Iโ€™m looking to collaborate on Fable Python
  • ๐Ÿค” Iโ€™m looking for help with Fable Python
  • ๐Ÿ’ฌ Ask me about working for Cognite
  • ๐Ÿ“ซ How to reach me: https://twitter.com/dbrattli
  • โšก Fun fact: I have cycled 2000 km from Windhoek (NA) to Cape Town (ZA) ... twice.

expression's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

expression's Issues

How about using TypeAlias with Result and Option?

Is your feature request related to a problem? Please describe.
I often use method "is_ok" and "is_error", but unfortunately, pylance does not recognize it

import typing as tp

import expression as ex


def get_result() -> ex.Result[int, tp.Any]:
    return ex.Ok(1)


a = get_result()
if a.is_ok():
    b = a  # in pylance:: (variable) b: Result[int, Any]
    # in pylance:: Cannot access member "value" for type "Result[int, Any]"
    #   Member "value" is unknown
    v = a.value
else:
    b = a  # in pylance:: (variable) b: Result[int, Any]
    # in pylance:: Cannot access member "error" for type "Result[int, Any]"
    #   Member "error" is unknown
    v = a.error

So I'm replacing it with "isinstance", but it's still a little disappointing

import typing as tp

import expression as ex


def get_result() -> ex.Result[int, tp.Any]:
    return ex.Ok(1)


a = get_result()
if isinstance(a, ex.Ok):
    b = a  # in pylance:: (variable) b: Ok[int, Any]
    v = a.value  # in pylance:: (variable) v: int
else:
    b = a  # in pylance:: (variable) b: Result[int, Any]
    # in pylance:: Cannot access member "error" for type "Result[int, Any]"
    #   Member "error" is unknown
    v = a.error

c = get_result()
if isinstance(c, ex.Error):
    d = c  # in pylance:: (variable) d: Error[int, Any]
    v = c.error  # in pylance:: (variable) v: Any
else:
    d = c  # in pylance:: (variable) d: Result[int, Any]
    # in pylance:: Cannot access member "value" for type "Result[int, Any]"
    #   Member "value" is unknown
    v = c.value

Describe the solution you'd like

It looks better when using TypeAlias

import typing as tp

import expression as ex
import typing_extensions as tpe

TValue = tp.TypeVar("TValue")
TError = tp.TypeVar("TError")

TypedResult: tpe.TypeAlias = tp.Union[ex.Ok[TValue, TError], ex.Error[TValue, TError]]


def get_typed_result() -> TypedResult[int, tp.Any]:
    return ex.Ok(1)


a2 = get_typed_result()
if isinstance(a2, ex.Ok):
    b2 = a2  # in pylance:: (variable) b2: Ok[int, Any]
    v2 = a2.value  # in pylance:: (variable) v2: int
else:
    b2 = a2  # in pylance:: (variable) b2: Error[int, Any]
    v2 = a2.error  # in pylance:: (variable) v2: Any

c2 = get_typed_result()
if isinstance(c2, ex.Error):
    d2 = c2  # in pylance:: (variable) d2: Error[int, Any]
    v2 = c2.error  # in pylance:: (variable) v2: Any
else:
    d2 = c2  # in pylance:: (variable) d2: Ok[int, Any]
    v2 = c2.value  # in pylance:: (variable) v2: int

If Result and Option were used simply to define the interface, I think its could be easily replaced. But I'm not sure, so I can't pr and just suggest it.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Python 3.10.9
vscode 1.74.3
pylance 2023.1.40

Seq.map, TypeError: map() missing 1 required positional argument: 'mapper'

Having some background in F# and just a beginner in Python, thought Expression lib worth exploring.

Using Ref https://cognitedata.github.io/Expression/expression/collections/seq.html and trying something as simple as:

    from expression.collections import Seq
    xs = Seq([1, 2, 3])
    ys = xs.pipe(
        Seq.map(lambda x: x + 1),
        Seq.filter(lambda x: x < 3)
    )

resulting in error:

  File "<ipython-input-54-c73530b41e79>", line 5, in <module>
      Seq.map(lambda x: x + 1),
  TypeError: map() missing 1 required positional argument: 'mapper'

Any help?

Is there some helper function to use parameters for function in Result?

Is your feature request related to a problem? Please describe.
There are times when I have to call a function in an uncertain state, and I use it in the following way.

import expression as ex


def func(a: int, b: str):
    return [a, b]


def get_func():
    return ex.Ok(func)


func_result = get_func()
value = func_result.map(lambda f: f(1, "test"))

It doesn't matter if it's a simple case.
But, if it overlaps a little bit, it's quite a hassle.

Describe the solution you'd like
I think there are two ways

import expression as ex


def func(a: int, b: str):
    return [a, b]


def get_func():
    return ex.Ok(func)


func_result = get_func()
value = helper_func(func_result, 1, "asd")

or

import typing as tp
import expression as ex


def func(a: int, b: str, c: ex.Result[int, tp.Any]):
    return [a, b]


def get_func():
    return ex.Ok(func)


func_result = get_func()

value = ex.pipe(
    func_result,
    func_helper.map(1),
    func_helper.map("asd"),
    func_helper.bind(123),
)

Describe alternatives you've considered
I'm using the second method as follows, but I don't think it's pythonic.

do not use below, too many bugs.

from functools import partial
from typing import Any, Callable, Union

from expression import Ok, Result, pipe
from expression.extra.result import catch
from typing_extensions import Concatenate, ParamSpec, TypeVar

ArgT = TypeVar("ArgT")
ParamT = ParamSpec("ParamT")
ResultT = TypeVar("ResultT")


class Currylike:
    @classmethod
    def map(
        cls, arg: ArgT
    ) -> Callable[
        [
            Union[
                Callable[Concatenate[ArgT, ParamT], ResultT],
                Result[Callable[Concatenate[ArgT, ParamT], ResultT], Any],
            ]
        ],
        Result[Callable[ParamT, ResultT], Any],
    ]:
        return partial(cls.fmap, arg)

    @classmethod
    def bind(
        cls, arg: ArgT
    ) -> Callable[
        [
            Union[
                Callable[Concatenate[Result[ArgT, Any], ParamT], ResultT],
                Result[Callable[Concatenate[Result[ArgT, Any], ParamT], ResultT], Any],
            ]
        ],
        Result[Callable[ParamT, ResultT], Any],
    ]:
        return partial(cls.fbind, arg)

    @staticmethod
    def fmap(
        arg: Union[ArgT, Result[ArgT, Any]],
        func: Union[
            Callable[Concatenate[ArgT, ParamT], ResultT],
            Result[Callable[Concatenate[ArgT, ParamT], ResultT], Any],
        ],
    ) -> Result[Callable[ParamT, ResultT], Any]:
        if isinstance(arg, Result):
            if isinstance(func, Result):
                return func.bind(lambda f: arg.map(lambda value: partial(f, value)))  # type: ignore

            return arg.map(lambda value: partial(func, value))  # type: ignore

        if isinstance(func, Result):
            return func.map(lambda f: partial(f, arg))  # type: ignore
        return Ok(partial(func, arg))  # type: ignore

    @staticmethod
    def fbind(
        arg: Union[ArgT, Result[ArgT, Any]],
        func: Union[
            Callable[Concatenate[Result[ArgT, Any], ParamT], ResultT],
            Result[Callable[Concatenate[Result[ArgT, Any], ParamT], ResultT], Any],
        ],
    ) -> Result[Callable[ParamT, ResultT], Any]:
        if isinstance(arg, Result):
            if isinstance(func, Result):
                return func.map(lambda f: partial(f, arg))  # type: ignore
            return Ok(partial(func, arg))  # type: ignore

        if isinstance(func, Result):
            return func.map(lambda f: partial(f, Ok(arg)))  # type: ignore
        return Ok(partial(func, Ok(arg)))  # type: ignore

    @staticmethod
    def fcall(
        func: Result[Callable[ParamT, ResultT], Any],
        *args: ParamT.args,
        **kwargs: ParamT.kwargs
    ) -> Result[ResultT, Any]:
        return func.bind(lambda f: catch(exception=Exception)(f)(*args, **kwargs))

def func(a: int, b: str, c: ex.Result[int, tp.Any]):
    return [a, b]


def get_func():
    return Ok(func)


func_result = get_func()

value = pipe(
    func_result,
    Currylike.map(1),
    Currylike.map("asd"),
    Currylike.bind(123),
    Currylike.fcall
)

I would appreciate it if you could let me know if there is a good method that you have already provided.

Additional context
Add any other context or screenshots about the feature request here.

__str__ and __repr__ of Failure & Success

Describe the bug
When using str or repr on Try, it prints the representations of the parent class

To Reproduce

from expression import Try, Success, Failure
print( Success(8) )
print( Failure(8) )

Output:
Ok 8
Error 8

Expected behavior
Output:
Success 8
Failure 8

Additional context

  • OS [e.g. WSL 2]
  • Expression version [e.g 4.3.0]
  • Python version [e.g. 3.10]

Type aliases using Union syntax incompatible with Python 3.9

Describe the bug
A basic Python script bombs when importing from expression.collections.

To Reproduce

  1. Install expression into Python 3.9 virtual environment using pip install.
  2. Create a basic top-level script that imports expression.collections.
  3. Run script with Python 3.9 from virtual environment.

Expected behavior
Python script runs without issue.

Code or Screenshots
Here is the code from my Python script:

from expression.collections import seq, Seq

if __name__ == '__main__':
    print("All done!")

Here is the traceback I get:

Traceback (most recent call last):
  File "/home/atonally/develop/sandbox-py39/main.py", line 3, in <module>
    from expression.collections import seq, Seq
  File "/home/atonally/develop/sandbox-py39/.venv/lib/python3.9/site-packages/expression/__init__.py", line 11, in <module>
    from . import collections, core, effect
  File "/home/atonally/develop/sandbox-py39/.venv/lib/python3.9/site-packages/expression/collections/__init__.py", line 4, in <module>
    from . import array, asyncseq, block, map, seq
  File "/home/atonally/develop/sandbox-py39/.venv/lib/python3.9/site-packages/expression/collections/array.py", line 28, in <module>
    from expression.core import (
  File "/home/atonally/develop/sandbox-py39/.venv/lib/python3.9/site-packages/expression/core/__init__.py", line 20, in <module>
    from .fn import TailCall, TailCallResult, tailrec, tailrec_async
  File "/home/atonally/develop/sandbox-py39/.venv/lib/python3.9/site-packages/expression/core/fn.py", line 24, in <module>
    TailCallResult = _TResult | TailCall[_P]
TypeError: unsupported operand type(s) for |: 'TypeVar' and '_GenericAlias'

Additional context

  • Kubuntu 22.04
  • Expression 4.2.2
  • Python 3.9.16

Try docs

Not sure if this is intentional, but the docs for the Try class are nearly identical to the docs for the Result class.

I would expect the source code reference for Try to show a type alias (Try = Result[TSource, Exception]) or sub-class definition.

https://cognitedata.github.io/Expression/expression/

Thank you!

In fact, I have been admiring the scala-style chaining function call and other functional programing concepts for a long time, but my usual work is all on python, Now I find I can achieve a good trade-off by combining your library and fn(used for simplifng lambda function into a blank space, sadly not maintained since 2014), Thank you again! Hope you can keep maintain and enpower this fantanstic object!

Issue on page /tutorial/effects.html

I am trying to reproduce the following code block from this tutorial:

from expression import effect
from expression.core import option, Option, Some, Nothing

def divide(a: float, divisor: float) -> Option[int]:
    try:
        return Some(a/divisor)
    except ZeroDivisionError:
        return Nothing


@effect.option
def comp(x):
    result = yield from divide(42, x)
    result += 32
    print(f"The result is {result}")
    return result

comp(42)

It returns the following on jupyter lab:

TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_477668/4077434771.py in <cell line: 11>()
     10 
     11 @effect.option
---> 12 def comp(x):
     13     result = yield from divide(42, x)
     14     result += 32

TypeError: OptionBuilder() takes no arguments

I couldn't make this code to work. Any suggestions?

Logo

Expression needs a logo. Anyone that wants to contribute to this great project?

How are the collections implemented?

I see that you provide different collections with immutability in mind.

Are they also implemented as immutable data structures?

If not, I recommend adding that info to the README section, that cares about the differences to FSharp.

In that case, I do suggest this as feature. :)

If it does, I recommend adding that info to the README as well.

Support for Either type

Is your feature request related to a problem? Please describe.

Coming from a bit of Haskell and more Scala, I was surprised to see there was no Either type. I'd like to see this added.

Describe the solution you'd like

I'd like to see the library have an Either type.

Describe alternatives you've considered

I'm aware that this library is supposed to be a more direct implementation of F# in Python than every type from other languages. Either is a pretty common concept (e.g., in Haskell and Scala), even if in Scala, Try is more frequently used. A lot of uses of Either will use Result or Try instead, but I think it would be good to have a generic Either for completeness, even if it's not part of F#.

Additional context

n/a

Possible wrong order of parent classes in Error?

Just noticed this playing with Try (Python 3.9.7, expression 1.1.0):

Python 3.9.7 (default, Aug 31 2021, 13:28:12) 
[GCC 11.1.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> python.el: native completion setup loaded
>>> from expression import Failure, Success
>>> x = Success(10)
>>> y = Failure(ValueError())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/vputz/.cache/pypoetry/virtualenvs/plugin-playground-TQ0vVnN3-py3.9/lib/python3.9/site-packages/expression/core/result.py", line 199, in __init__
    super().__init__(str(error))
TypeError: object.__init__() takes exactly one argument (the instance to initialize)

Looks like in result.py:199:


class Error(Result[TSource, TError], ResultException):
    """The Error result case class."""

    def __init__(self, error: TError) -> None:
        super().__init__(str(error))
        self._error = error

... it looks like it's trying to call the Result version of __init__ first (which makes sense; it's first in the parent list) instead of Error.__init__, which indeed takes a string as an argument.

Changing the order of parents to:

class Error(ResultException, Result[TSource, TError]):

Avoids the error:

>>> from expression import Failure, Success
>>> x = Success(10)
>>> y = Failure(ValueError())

...although there may be another way to resolve it which may be preferable.

Nothing_.__iter__ method not typed correctly?

The example from the tutorial does not type check with Pylance:

from expression import effect, Some, Nothing

@effect.option
def fn():
    x = yield from Nothing # or a function returning Nothing

    # -- The rest of the function will never be executed --
    y = yield from Some(43)

    return x + y

xs = fn()
assert xs is Nothing

Type checking fails at x = yield from Nothing -- Pylance thinks it is Unknown type. However, if I change the return type of Nothing_.__iter__ to be like the return type from Some:

    def __iter__(self) -> Generator[TSource, TSource, TSource]:

Then x = yield from Nothing type checks to an Any. Which, given the type of Nothing seems correct. Though of course nothing will yield from Nothing because of the raise Nothing in __iter__.

However, I don't know if changing the return type to Generator has negative consequences in other contexts.

4.2.0 has invalid python 3.9 syntax

Describe the bug
https://github.com/cognitedata/Expression/blob/19637ad4b033dc7a454ef7c1bd7164743b782334/expression/core/result.py#L199 uses a match syntax that's only available in python >= 3.10, but the expression package is marked as compatible with python >= 3.9.

To Reproduce

$ python3 --version
Python 3.9.14
$ mkdir /tmp/repro \
      && cd /tmp/repro \
      && python3 -m venv .venv \
      && source .venv/bin/activate
$ pip install expression==4.2.0
$ echo "import expression" >> repro.py
$ python3 repro.py
Traceback (most recent call last):
  File "/private/tmp/repro/repro.py", line 1, in <module>
    import expression
  File "/private/tmp/repro/.venv/lib/python3.9/site-packages/expression/__init__.py", line 11, in <module>
    from . import collections, core, effect
  File "/private/tmp/repro/.venv/lib/python3.9/site-packages/expression/collections/__init__.py", line 4, in <module>
    from . import array, asyncseq, block, map, seq
  File "/private/tmp/repro/.venv/lib/python3.9/site-packages/expression/collections/array.py", line 28, in <module>
    from expression.core import (
  File "/private/tmp/repro/.venv/lib/python3.9/site-packages/expression/core/__init__.py", line 5, in <module>
    from . import aiotools, option, result
  File "/private/tmp/repro/.venv/lib/python3.9/site-packages/expression/core/result.py", line 199
    match other:
          ^
SyntaxError: invalid syntax

And thank you for all your work maintaining this package. :)

_TSource in Option should be covariant

Describe the bug
_TSource type in Option should be covariant.

class A:
  pass
class B(A):
  pass

x: Option[A] = Some(B())

Now since the _TSource is invariant, pyright will complain that B is not the same as A, but actually Option type should be covariant. Is Some[Dog] a Some[Animal]? I think you'll agree that the answer is yes. Also Option type is not actually mutable, so there is no way to assign a Some[Dog] to a variable typed Some[Aninmal] and then make it become Some[Cat]

Additional context
Add any other context about the problem here.

  • OS Linux
  • Expression version 4.2.4
  • Python version 3.11

Explore better typing for comprehensions / computational expressions

Consider:

    @effect.option
    def fn() -> Generator[Any, Any, List[str]]:
        x: int = yield 42
        y: str = yield f"{x} as a string"
        z: List[str] = yield from Some([y, str(x)])
        return z

Currently, unless each expression has the same type, you're stuck either being untyped or Any-typed and manually typing each bound name. This is really unfortunate and not at all type-safe.

Chaining a bunch of transformations of values within a functor or monad is a very common use of Haskell do-expressions, Scala for-expressions, and presumably (although i have no first hand knowledge) F# computational expressions.

It's really nice to be able to chain these and rely on types to ensure the correctness of each step.

With the current approach, it doesn't seem possible to type the individual layers of unwrapping differently unless they all share the same contained type (for the contravariant type parameter). This is really limiting the usefulness, alas.

Context: I'm trying to introduce some good foundations and abstractions for correctness at my company, and I think Expression could be a part of this based on its trajectory. However, the current limitations/ergonomics like this, combined with limitations in mypy would make this a somewhat difficult sell, so I'm hoping there's a better approach or some ideas for improvement. Happy to assist where possible, but i'm definitely not an expert in python or python typing.

Short-circuit does not work for me in pipe()

Describe the bug
Short-circuit does not work for me in pipe().
Maybe i'm using this function in a wrong way. If so, please tell me how it should be.

Thank you.

To Reproduce
Execute this code with Expression 2.0.0:

from expression import pipe, effect, Ok
from expression.core.option import Nothing


@effect.result[int, Exception]()
def mulby10(x):
    yield from Ok(x * 10)
 
@effect.option[int]()
def divbyzero(x):
    try:
        yield from Ok(x / 0)
    except Exception as exn:
        yield from Nothing

def main():
    v = 1
    res = pipe(
        v,
        divbyzero,
        mulby10
        )
    print(f"{res=}")

if __name__ == "__main__":
    main()

It executes mulby10 after divbyzero returns Nothing and shows error:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/Users/davaeron/repos/automation/automation/cli.py", line 18, in main
    res = pipe(
  File "/Users/davaeron/repos/automation/__pypackages__/3.10/lib/expression/core/pipe.py", line 136, in pipe
    return compose(*fns)(value)
  File "/Users/davaeron/repos/automation/__pypackages__/3.10/lib/expression/core/compose.py", line 136, in _compose
    return reduce(lambda acc, f: f(acc), fns, source)
  File "/Users/davaeron/repos/automation/__pypackages__/3.10/lib/expression/core/compose.py", line 136, in <lambda>
    return reduce(lambda acc, f: f(acc), fns, source)
  File "/Users/davaeron/repos/automation/__pypackages__/3.10/lib/expression/core/builder.py", line 96, in wrapper
    result = self._send(gen, done)
  File "/Users/davaeron/repos/automation/__pypackages__/3.10/lib/expression/core/builder.py", line 52, in _send
    yielded = gen.send(value)
  File "/Users/davaeron/repos/automation/automation/cli.py", line 7, in mulby10
    yield from Ok(x * 10)
TypeError: unsupported operand type(s) for *: 'Nothing_' and 'int'

Expected behavior
res=Nothing after divbyzero function, mulby10 should be unexecuted

Additional context

  • OS MacOS 12.4
  • Expression version: 2.0.0
  • Python version: 3.10.4

Seq and generators

Hi!

Is there a way to map over generators? Can seq help? For example:

YEARS:List[int] = [2015, 2016, 2017, 2018, 2019, 2020, 2021]

def get_train_test_years():
    for i in range(3, len(YEARS)):
        yield YEARS[:i], YEARS[i]

def transform(x,y):
    return x+"Arbitrary complex",y+"Arbitrary complex"

#Does not work
ys = pipe(get_train_test_years(),
    seq.map(transform)
)

# works but repetitive
test_train_candidates  = (transform(train_years, test_year) for train_years, test_year in get_train_test_years())

Thanks!

change function for map does not appear to work correctly

Describe the bug
This may not be a bug, but I can't find documentation for the change function / method as it applies to maps. It certainly does not appear to operate in a way that I would expect.

Calling change on an existing, nonempty map appears to operate as if it were called on an empty map.

To Reproduce

This results in a Nothing_ error, when I would expect it to return the map unchanged:

from expression.collections import Map

xs = Map.of(**dict(a=10, b=20))
xs.change("a", lambda x: x)

This results in map [("a", 1)] when I would expect map [("a", 1); ("b", 20)]:

xs.change("a", lambda _: Some(1))

Expected behavior
As indicated above, I would expect that the first block would return the map unchanged, and that the second would only modify the item with the key "a".

Additional context
Add any other context about the problem here.

  • OS MacOS 13
  • Expression version 4.2.2
  • Python version 3.11.1

@effect.result decorator return type

I've started plumbing the Try type throughout a program I'm working on for better error handling :)

I just realized that the @result decorator drops type information (by returning a ResultBuilder[Any, Any]).

I'm not sure if a decorator function could be written such that the type checker will infer the resulting (decorated) function's return type based on the input function's return type, which would be ideal. It seems like that should be possible, since the function-to-be-decorated (and its type annotation) will be available to the decorator function, but I haven't tried it.

But even if that's not possible, I'd rather pass type information around explicitly than drop it (via Any).

By defining this try_of helper, I can (with a bit of boilerplate) recover the missing type information.

@effect.result
def surprise() -> Try[List[str]]:
    yield from Success(["Good", "luck", "!"])


def try_of(_: Type[T]) -> ResultBuilder[T, Exception]:
    return effect.result


@try_of(List[str])
def list_of_str() -> Try[List[str]]:
    yield from Success(["well", "typed", ":)"])


# not sure if I love it, but this seems to work, too
@try_of(List[str])
def list_of_str_without_explicit_return_type_annotation():
    yield from Success(["still", "well", "typed", ":)"])


@effect.result
def caller():
    yield from (
        a + b + c
        for a in list_of_str()  # pyright infers a: List[str]
        for b in surprise()  # pyright infers b: Any
        for c in list_of_str_without_explicit_return_type_annotation()  # pyright infers c: List[str]
    )

Any thoughts? Is there an easier way to do this already?

Adding type annotations to curried functions

From what I can see in core/curry.py, there's no way to specify type annotations on-the-fly in a meaningful way. However, instead of recommending against usage, is it worth providing a @functools.wraps decorator over the called function so some notion of annotation and docstring is preserved?

It trusts that the user knows what they're doing, of course :)

README unclear about where 'seq' comes from

This looks like an impressive project and I'm definitely looking at it for a health IT project, but I have a question, and maybe this can simulate a fresh look at the docs:

My first noobie reaction to this was, "where does 'seq' come from?" and to scroll up and it's never mentioned before here, and not explicitly defined.

ys = xs.pipe(
    seq.map(lambda x: x * 10),
    seq.filter(lambda x: x > 100),
    seq.fold(lambda s, x: s + x, 0)
)

No big deal, but the README is the main entrypoint to the project for noobs like me
Thank you for your work and thank you for taking the time to read this

Fishy locking in CancellationSource

Describe the bug
Usage of _lock in https://github.com/cognitedata/Expression/blob/main/expression/system/cancellation.py doesn't look correct.

I was looking through files and got puzzled by how CancellationTokenSource.dispose() is implemented.

Apparently it tries to read _is_disposed under the lock but on the next like assigns it without lock. It also calls listeners without lock.

On the other hand register_internal seems to do the opposite: it reads _is_disposed without lock and accesses _listeners inside a critical section.

To be strict all access to variables should be protected and protected consistently. E.g. if it's dangerous to call a callback under the lock - a copy (slice) of _listeners could be made (in the same critical section with checking for _is_disposed).

seq.map does not type check correctly

Describe the bug

The functional pipe examples where seq.map is the first function in the pipe causes a type error.

To Reproduce
Steps to reproduce the behavior:

Screenshot 2023-07-26 at 17 58 18

Expected behavior

pyright type checks correctly

Code or Screenshots

This causes type error:

from expression.collections import seq, Seq

xs = Seq.of(9, 10, 11)
ys = xs.pipe(
    seq.map(lambda x: x * 10),
    seq.filter(lambda x: x > 100),
    seq.fold(lambda s, x: s + x, 0)
)

This passes:

from expression.collections import seq, Seq

xs = Seq.of(9, 10, 11)
ys = xs.pipe(
    seq.filter(lambda x: x > 100),
    seq.map(lambda x: x * 10),
    seq.fold(lambda s, x: s + x, 0)
)

Additional context
Add any other context about the problem here.

  • MacOS
  • Expression version 4.2.4
  • Tested on Python 3.11.4 and 3.10.12

NonEmptyList

Is your feature request related to a problem? Please describe.
I have found NonEmptyList from the Scala Cats library very useful in the past. I'd like to implement a similar collection type in Expression. Is this something you would be open to @dbrattli ?

Describe the solution you'd like
Just a few examples outlined below. Overall I'd see it working in a similar way to expression.collections.Block (i.e. an immutable collection).

list(NonEmptyList.one(1)) == [1]

list(NonEmptyList.of(1, 2, 3)) == [1, 2, 3]

NonEmptyList.from([1, 2, 3]).map(list) == Some([1, 2, 3])

NonEmptyList.from([]) == Nothing

def do_something_with_list_that_must_be_non_empty(l: NonEmptyList):
    # do things with elements in `l`

Describe alternatives you've considered
Doing this everywhere in your code:

def do_something_with_list_that_must_be_non_empty(l: list):
    if l:
        # do things with elements in `l`

Improve the ergonomics of pipelines

Is your feature request related to a problem? Please describe.
In F# one can write the following pipeline:

let result =
  (val1, val2, val3)
  |> convertVal1  // A * B * C -> (P * Q) * B * C
  |> convertVal2  // (P * Q) * B * C -> S * C
  |> convertVal3  // S * C -> S * T

With Expression I was able to implement it in two non-ergonomic ways:

result = pipe(
    (val1, val2, val3),
    lambda x: starpipe(x, convertVal1),
    lambda x: starpipe(x, convertVal2),
    lambda x: starpipe(x, convertVal3)
)

result = starpipe(
    starpipe(
        starpipe(
            (val1, val2, val3),
            convertVal1
        ),
        convertVal2
    ),
    convertVal3
)

Describe the solution you'd like
I would like to write the same code as in F#.

Describe alternatives you've considered
Perhaps I am missing something and Expression does allow writing something similar to the F# code.

Additional context

Automatically catch and wrap exceptions thrown in @effect.result functions?

Currently, it looks like any non-Expression exceptions thrown in a effect.result decorated function is re-raised by the decorator. I'm wondering if it might be useful to have the decorator return an Error object instead, wrapping the raised Exception object?

This would reduce the amount of manual conversions from raised exceptions to Error objects, enabling easier interfacing with code/libraries that throw exceptions.

Thoughts? Happy to take a stab at implementing this. Either way, I'm digging this library!

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Other Branches

These updates are pending. To force PRs open, click the checkbox below.

  • chore(deps): update dependency sphinx-autodoc-typehints to v1.24.0

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

github-actions
.github/workflows/python-package.yml
  • actions/checkout v3
  • actions/setup-python v4
.github/workflows/python-publish.yml
  • actions/checkout v3
  • actions/setup-python v4
pip_requirements
docs/requirements.txt
  • sphinx-autodoc-typehints >=1.17.0
  • expression >=2.0.1
poetry
pyproject.toml
  • typing-extensions ^4.1.1
  • pytest-asyncio ^0.21.0
  • pytest ^7.2.0
  • coverage ^6.4.3
  • black ^23.0.0
  • isort ^5.10.1
  • flake8 ^6.0.0
  • coveralls ^3.3.1
  • pre-commit ^3.0.0
  • autoflake ^2.0
  • dunamai ^1.12.0
  • hypothesis ^6.54.2
  • jupyter-book ^0.15.0
  • sphinx-autodoc-typehints ^1.17.0
  • pydantic ^1.10.0

  • Check this box to trigger a request for Renovate to run again on this repository

Map change drops current items when len is two

When adding a new item to a Map having 2 items, using change method adds the new item but drops the items it had.

Map.empty().change(1, lambda _: Some(1)).change(2, lambda _: Some(2)).change(3, lambda _: Some(3))

A Map having 3 items is expected, but actually the resulting Map only has (3, 3).

image

  • OS [MacOS]
  • Expression version [4.2.2]
  • Python version [3.11.1]

Generic monad algorithm

Is it possible to define a function that can handle multiple types of effects depends on the object that yields the effect?
Like in Haskell

join :: Monad m => m (m a) -> m a
join xss = 
    do xs <- xss
       xs                            

which would work for all monads including, list, maybe, io, stm, etc...
I can help about it if needed.

Pipeline

I understand your decision, to not use operator overloading.
To frame my following opposition to it, here my background:

I can not use FSharp in my current project, since it is barely implemented.

Python is an alternative. Something like Coconut is awesome, but its editor support is limited and I like good syntax highlighting.

After looking for a good hour through all kinds of Stackoverflow posts, PyPi, and several other sources to find at least a proper pipe, did I finally stumble over your project and was finally in Aww O.O

I finally found it, and then I read this:

Screenshot_2020-10-11-11-14-58-81

Just to be honest: This is how F# code looks like:

Screenshot_2020-10-11-11-36-31-62

I dont see any sense in a FSharp library for Python, that does not allow me to do that.

I can not emphasis enough, how important the pipeline operator is for me, its just fundamental to how FSharp feels and works for me. :)

Especially to place them into a new line, which is how idiomatic FSharp looks and feels.

Its also easier, to apply tutorials.

I understand that |> is probably not possible without recompiling and that overloading an already used operator is confusing, while I strongly suggest to adopt one that is not in broader use, or give the option to use | or >> anyway

I really see the point of avoiding confusion, while I dont work in a team - like most coders, especially in the open-source field - and reading into code based is always a struggle and this makes code at least more unambiguous and it's important to me, to be able to use FSharps traditional method.

It confuses me much more, to do it the way you implemented it.

Thanks a lot!

Other F# default Seq functions

The BCL F# Seq module has some other really helpful functions, do you have plans to implement them? need help/accept PR?

average
averageBy
cache
countBy
distinct
distinctBy
exactlyOne
exists
exists2
forall
forall2
groupBy
iteri
last
maxBy
minBy
nth
pairwise
pick
reduce
skipWhile
sort
sortBy
takeWhile
truncate
tryFind
tryFindIndex
tryPick
windowed
zip3

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.