Closed pertsevds closed 1 year ago
Tried Expression v3.3.1 - same result.
Hi @davaeron
If you change your code a bit you should see the error in pylance (vscode):
from expression import pipe, effect, Ok
from expression.core.option import Nothing
@effect.result[int, Exception]()
def mulby10(x: int):
yield from Ok(x * 10)
@effect.option[int]()
def divbyzero(x: int):
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()
That is an Option[int]
cannot be used for a int
. Thus the second function mulby10
expects an integer but the first function returns an Option[int]
.
To fix you need mulby10
to take an option value (python 3.10 example):
from expression import pipe, effect, Ok, Option, Nothing, Some
@effect.result[int, Exception]()
def mulby10(x: Option[int]):
match x:
case Some(value):
yield from Ok(value * 10)
case _:
yield from Nothing
@effect.option[int]()
def divbyzero(x: int):
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()
from expression import pipe, effect, Ok, Nothing, option
@effect.option[int]()
def mulby10(x: int):
yield x * 10
@effect.option[int]()
def divbyzero(x: int):
try:
yield (x // 0)
except Exception:
yield from Nothing
def main():
v = 1
res = pipe(
v,
divbyzero,
option.bind(mulby10),
)
print(f"{res=}")
if __name__ == "__main__":
main()
Interesting.
from expression import pipe, effect, result, Error, Some, Ok
class DivisionByZero(Error):
def __init__(self, error="Division by zero"):
super().__init__(error)
def bind(self, mapper):
return DivisionByZero(self._error)
class OtherError(Error):
def __init__(self, error="Other error"):
super().__init__(error)
def bind(self, mapper):
return OtherError(self._error)
@effect.result[int, Exception]()
def mulby10(x: int):
try:
yield (x * 10)
except Exception:
yield from OtherError()
@effect.result[int, Exception]()
def divbyzero(x: int):
try:
yield (x // 0)
except Exception:
yield from DivisionByZero()
def main():
v = Ok(1)
res = pipe(
v,
result.bind(divbyzero),
result.bind(mulby10)
)
match res:
case Some(value):
print(f"{value=}")
case DivisionByZero(e) | OtherError(e):
print(e)
case _:
print("Not implemented.")
if __name__ == "__main__":
main()
But what if i don't want to always call "result.bind"?
from functools import reduce
from typing import Any
from expression import effect, Error, Ok
class DivisionByZeroError(Error):
def __init__(self, error="Division by zero"):
super().__init__(error)
def bind(self, mapper):
return self
class OtherError(Error):
def __init__(self, error="Other error"):
super().__init__(error)
def bind(self, mapper):
return self
@effect.result[int, Exception]()
def mulby10(x: int):
try:
yield (x * 10)
except Exception:
yield from OtherError()
@effect.result[int, Exception]()
def divbyzero(x: int):
try:
yield (x // 0)
except Exception:
yield from DivisionByZeroError()
def bound_compose(*fns):
def _compose(source: Any) -> Any:
match source:
case Ok():
return reduce(lambda acc, f: acc.bind(f), fns, source)
case Exception():
return source
case _:
return reduce(lambda acc, f: acc.bind(f), fns, Ok(source))
return _compose
def bound_pipe(__value: Any, *fns) -> Any:
return bound_compose(*fns)(__value)
def main():
v = 1
res = bound_pipe(
v,
divbyzero,
mulby10
)
match res:
case Ok(value):
print(f"{value=}")
case DivisionByZeroError(e) | OtherError(e):
print(e)
case _:
print("Not implemented.")
if __name__ == "__main__":
main()
Something like this? Does this looks reasonable?
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:
It executes mulby10 after divbyzero returns Nothing and shows error:
Expected behavior res=Nothing after divbyzero function, mulby10 should be unexecuted
Additional context