Closed thomasborgen closed 4 years ago
Thanks a lot for asking this question! Yes, we indeed need to specify this batter in the docs.
The usecase for map
is when we compose a container and a pure function. A pure function is somethings that does not have any side-effects. Like abs
or min
or str
. You can also write your own pure functions.
But, when you use json.loads
- you have to use bind(safe(...))
, why? Because json.loads
is impure. It raises an exception. And the whole point of Result
is to convert exceptions to values. That's what we do with bind(safe(...))
.
I hope that my explanation makes sence.
TLDR: compose pure functions with map
and turn raising ones into Result
and use bind
Hi again and thanks for explaining, it makes more sense now and yes it would be nice with some extended docs.
I have a follow up question.
abs
and min
Are pure because whatever we put into them will give the same result always and it doesn't affect any globals. I agree with that, but at the same time abs('bob')
and min(1)
do raise exceptions.
So when they raise exceptions should Returns handle them? Or should it be the programmers job to make sure that the value returned above must be the correct valuetype, range, etc.? This would mean that you would have to check the types and values before sending it to a pure function with .map. Does this defeat the purpose a little bit? I really like the idea that we can call a function and if it fails we'll know it and can handle it accordingly with rescue, fix, alt. Handling and checking values before running some code seems counterintuitive for ROP.
TLDR: pure functions can still raise exceptions right? how should they be handled?
So when they raise exceptions should Returns handle them?
No, because these are different kinds of exceptions. They indicate an invalid use. Basically you should never catch TypeError
/ SyntaxError
inside your Result
. They will be caught during typecheck anyway:
» mypy ex.py --pretty
ex.py:1: error: Argument 1 to "abs" has incompatible type "str"; expected
"SupportsAbs[<nothing>]"
abs('a')
^
ex.py:2: error: No overload variant of "min" matches argument type "int"
min(1)
^
ex.py:2: note: Possible overload variant:
ex.py:2: note: def [_T] min(Iterable[_T], *, key: Callable[[_T], Any] = ...) -> _T
ex.py:2: note: <2 more non-matching overloads not shown>
Found 2 errors in 1 file (checked 1 source file)
Ofcourse! I didnt think about that :) Thank you for clearing stuff up!
Hi, first I just want to thank you for this awesome library :)
I'm wondering about the map_ function and its unsafe usage. I might be doing this wrong, if so please correct me!
So if i'm making a flow i could do something like this:
Lets say reading the file works nicely, but actually the json is malformed which makes map_(json.loads) throw an exception that is not caught by the flow. It will also not end up in any
alt
norrescue
parts of the flow.Another example:
changing from
map(int)
tobind(safe(int))
does work, but then we're back to usingbind
.So my question is what is the usecase for .map and map_ if they cannot be called safely?
Now this library is all about Railway Oriented Programming and I think it would make sense to catch the exceptions in the examples and return a Failure, which then can propogate down to
alt
orrescue
part of the flow. How this should be done idk, but basically the same as the @safe annotation does would be nice.I also think this makes sense since it would unify what we are working on when writing flows. that the map function returns a Modal will make it similar to bind. and nested Modals won't be something you'd have to think about that much.
I might be doing things wrong but basically all functions or callable objects i write when using this library returns a Modal. So when composing those with flow or pipe I will be using bind. Only when calling builtins I will use map. So I believe that builtins or other functions from other libraries that does not return a modal should be forced to do it in a flow. And the way to force it should be with
map_
/.map