@@ -52,7 +52,7 @@ Make sure you know how to get started, [check out our docs](https://returns.read
52
52
- [ Maybe container] ( #maybe-container ) that allows you to write ` None ` -free code
53
53
- [ RequiresContext container] ( #requirescontext-container ) that allows you to use typed functional dependency injection
54
54
- [ Result container] ( #result-container ) that let's you to get rid of exceptions
55
- - [ IO marker] ( #io-marker ) that marks all impure operations and structures them
55
+ - [ IO marker] ( #io-marker ) and [ IOResult ] ( #troublesome-io ) that marks all impure operations and structures them
56
56
57
57
58
58
## Maybe container
@@ -62,7 +62,7 @@ Make sure you know how to get started, [check out our docs](https://returns.read
62
62
So, what can we do to check for ` None ` in our programs?
63
63
You can use builtin [ Optional] ( https://mypy.readthedocs.io/en/stable/kinds_of_types.html#optional-types-and-the-none-type ) type
64
64
and write a lot of ` if some is not None: ` conditions.
65
- But, having them here and there makes your code unreadable.
65
+ But, ** having ` null ` checks here and there makes your code unreadable** .
66
66
67
67
``` python
68
68
user: Optional[User]
@@ -167,7 +167,7 @@ Large code bases will struggle a lot from this change.
167
167
168
168
Ok, you can directly use ` django.settings ` (or similar)
169
169
in your ` _award_points_for_letters ` function.
170
- And ruin your pure logic with framework specific details. That's ugly!
170
+ And ** ruin your pure logic with framework specific details** . That's ugly!
171
171
172
172
Or you can use ` RequiresContext ` container. Let's see how our code changes:
173
173
@@ -281,6 +281,7 @@ def fetch_user_profile(user_id: int) -> Result['UserProfile', Exception]:
281
281
282
282
@safe
283
283
def _make_request (user_id : int ) -> requests.Response:
284
+ # TODO : we are not yet done with this example, read more about `IO`:
284
285
response = requests.get(' /api/users/{0} ' .format(user_id))
285
286
response.raise_for_status()
286
287
return response
@@ -291,43 +292,37 @@ def _parse_json(response: requests.Response) -> 'UserProfile':
291
292
```
292
293
293
294
Now we have a clean and a safe and declarative way
294
- to express our business need.
295
- We start from making a request, that might fail at any moment.
296
- Then parsing the response if the request was successful.
297
- And then return the result.
298
- It all happens smoothly due to [ pipe] ( https://returns.readthedocs.io/en/latest/pages/pipeline.html#pipe ) function.
295
+ to express our business needs:
299
296
300
- We also use [ box] ( https://returns.readthedocs.io/en/latest/pages/functions.html#box ) for handy composition.
297
+ - We start from making a request, that might fail at any moment,
298
+ - Then parsing the response if the request was successful,
299
+ - And then return the result.
301
300
302
- Now, instead of returning a regular value
303
- it returns a wrapped value inside a special container
301
+ Now, instead of returning regular values
302
+ we return values wrapped inside a special container
304
303
thanks to the
305
304
[ @safe ] ( https://returns.readthedocs.io/en/latest/pages/result.html#safe )
306
- decorator.
305
+ decorator. It will return [ Success[ YourType] or Failure[ Exception]] ( https://returns.readthedocs.io/en/latest/pages/result.html ) .
306
+ And will never throw exception at us!
307
307
308
- It will return [ Success[ Response] or Failure[ Exception]] ( https://returns.readthedocs.io/en/latest/pages/result.html ) .
309
- And will never throw this exception at us.
308
+ We also use [ pipe] ( https://returns.readthedocs.io/en/latest/pages/pipeline.html#pipe )
309
+ and [ bind] ( https://returns.readthedocs.io/en/latest/pages/pointfree.html#bind )
310
+ functions for handy and declarative composition.
310
311
311
- And we can clearly see all result patterns
312
- that might happen in this particular case:
312
+ This way we can be sure that our code won't break in
313
+ random places due to some implicit exception.
314
+ Now we control all parts and are prepared for the explicit errors.
313
315
314
- - ` Success[UserProfile] `
315
- - ` Failure[Exception] `
316
-
317
- For more complex cases there's a [ @pipeline ] ( https://returns.readthedocs.io/en/latest/pages/functions.html#returns.functions.pipeline )
318
- decorator to help you with the composition.
319
-
320
- And we can work with each of them precisely.
321
- It is a good practice to create ` Enum ` classes or ` Union ` sum type
322
- with all the possible errors.
316
+ We are not yet done with this example,
317
+ let's continue to improve it in the next chapter.
323
318
324
319
325
320
## IO marker
326
321
327
- But is that all we can improve?
328
- Let's look at ` FetchUserProfile ` from another angle.
329
- All its methods look like regular ones:
330
- it is impossible to tell whether they are pure or impure from the first sight.
322
+ Let's look at our example from another angle.
323
+ All its functions look like regular ones:
324
+ it is impossible to tell whether they are [ pure ] ( https://en.wikipedia.org/wiki/Pure_function )
325
+ or impure from the first sight.
331
326
332
327
It leads to a very important consequence:
333
328
* we start to mix pure and impure code together* .
@@ -338,10 +333,59 @@ we suffer really bad when testing or reusing it.
338
333
Almost everything should be pure by default.
339
334
And we should explicitly mark impure parts of the program.
340
335
336
+ That's why we have created ` IO ` marker
337
+ to mark impure functions that never fail.
338
+
339
+ These impure functions use ` random ` , current datetime, environment, or console:
340
+
341
+ ``` python
342
+ import random
343
+ import datetime as dt
344
+
345
+ from returns.io import IO
346
+
347
+ def get_random_number () -> IO [int ]: # or use `@impure` decorator
348
+ return IO(random.randint(1 , 10 )) # isn't pure, because random
349
+
350
+ now: Callable[[], IO [dt.datetime]] = impure(dt.datetime.now)
351
+
352
+ @impure
353
+ def return_and_show_next_number (previous : int ) -> int :
354
+ next_number = previous + 1
355
+ print (next_number) # isn't pure, because does IO
356
+ return next_number
357
+ ```
358
+
359
+ Now we can clearly see which functions are pure and which ones are impure.
360
+ This helps us a lot in building large applications, unit testing you code,
361
+ and composing bussiness logic together.
362
+
363
+ ### Troublesome IO
364
+
365
+ As it was already said, we use ` IO ` when we handle functions that do not fail.
366
+
367
+ What if our function can fail and is impure?
368
+ Like ` requests.get() ` we had earlier in your example.
369
+
370
+ Then we have to use ` IOResult ` instead of a regular ` Result ` .
371
+ Let's find the difference:
372
+
373
+ - Our ` _parse_json ` function always return
374
+ the same result (hopefully) for the same input:
375
+ you can either parse valid ` json ` or fail on invalid one.
376
+ That's why we return pure ` Result `
377
+ - Our ` _make_request ` function is impure and can fail.
378
+ Try to send two similar requests with and without internet connection.
379
+ The result will be different for the same input.
380
+ That's why we must use ` IOResult ` here
381
+
382
+ So, in order to fulfill our requirement and separate pure code from impure one,
383
+ we have to refactor our example.
384
+
341
385
### Explicit IO
342
386
343
- Let's refactor it to make our
344
- [ IO ] ( https://returns.readthedocs.io/en/latest/pages/io.html ) explicit!
387
+ Let's make our [ IO ] ( https://returns.readthedocs.io/en/latest/pages/io.html )
388
+ explicit!
345
389
346
390
``` python
347
391
import requests
@@ -372,12 +416,14 @@ def _parse_json(response: requests.Response) -> 'UserProfile':
372
416
return response.json()
373
417
```
374
418
375
- Now we have explicit markers where the ` IO ` did happen
376
- and these markers cannot be removed.
419
+ And latter we can [ unsafe_perform_io] ( https://returns.readthedocs.io/en/latest/pages/io.html#unsafe-perform-io )
420
+ somewhere at the top level of our program to get the pure value.
421
+
422
+ As a result of this refactoring session, we know everything about our code:
377
423
378
- Whenever we access ` FetchUserProfile ` we now know
379
- that it does ` IO ` and might fail.
380
- So, we act accordingly!
424
+ - Which parts can fail,
425
+ - Which parts are impure,
426
+ - How to compose them in a smart manner.
381
427
382
428
383
429
## More!
0 commit comments