trcks 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
trcks/__init__.py ADDED
@@ -0,0 +1,123 @@
1
+ """Typesafe railway-oriented programming (ROP).
2
+
3
+ This package provides
4
+
5
+ - the generic (return) types `trcks.Result` and `trcks.AwaitableResult` and
6
+ - the subpackages `trcks.fp` and `trcks.oop` for working with these types
7
+ in a functional and object-oriented way, respectively.
8
+
9
+ See:
10
+ https://fsharpforfunandprofit.com/posts/recipe-part2/
11
+ """
12
+
13
+ from collections.abc import Awaitable
14
+ from typing import Literal, Union
15
+
16
+ from trcks._typing import TypeAlias, TypeVar
17
+
18
+ __docformat__ = "google"
19
+
20
+ _F_co = TypeVar("_F_co", covariant=True)
21
+ _S_co = TypeVar("_S_co", covariant=True)
22
+
23
+
24
+ Failure: TypeAlias = tuple[Literal["failure"], _F_co]
25
+ """`tuple` of length 2 containing ``"failure"`` followed by a value of type `_F_co`.
26
+
27
+ Example:
28
+ >>> failure: Failure[str] = ("failure", "File does not exist")
29
+
30
+ Note:
31
+ This generic type is called "Left" in
32
+ some functional programming languages and packages (e.g. Haskell and fp-ts).
33
+ """
34
+
35
+ Success: TypeAlias = tuple[Literal["success"], _S_co]
36
+ """`tuple` of length 2 containing ``"success"`` followed by a value of type `_S_co`.
37
+
38
+ Example:
39
+ >>> success: Success[int] = ("success", 42)
40
+
41
+ Note:
42
+ This generic type is called "Right" in
43
+ some functional programming languages and packages (e.g. Haskell and fp-ts).
44
+ """
45
+
46
+ Result: TypeAlias = Union[Failure[_F_co], Success[_S_co]]
47
+ """Discriminated union of the generic types `_F_co` and `_S_co`.
48
+
49
+ Can be used as a return type of a function
50
+ instead of returning `_S_co` and raising `_F_co`.
51
+
52
+ Example:
53
+ >>> def divide(a: float, b: float) -> Result[ZeroDivisionError, float]:
54
+ ... try:
55
+ ... return ("success", a/b)
56
+ ... except ZeroDivisionError as e:
57
+ ... return ("failure", e)
58
+ ...
59
+ >>> divide(5.0, 2.0)
60
+ ('success', 2.5)
61
+ >>> divide(3.5, 0.0)
62
+ ('failure', ZeroDivisionError('float division by zero'))
63
+
64
+ Note:
65
+ This generic type is called "Either" in
66
+ some functional programming languages and packages (e.g. Haskell and fp-ts).
67
+ """
68
+
69
+ AwaitableFailure: TypeAlias = Awaitable[Failure[_F_co]]
70
+ """`collections.abc.Awaitable` that returns a `Failure`
71
+ when used in an ``await`` expression.
72
+ """
73
+
74
+ AwaitableSuccess: TypeAlias = Awaitable[Success[_S_co]]
75
+ """`collections.abc.Awaitable` that returns a `Success`
76
+ when used in an ``await`` expression.
77
+ """
78
+
79
+ AwaitableResult: TypeAlias = Awaitable[Result[_F_co, _S_co]]
80
+ """`collections.abc.Awaitable` that returns a `Result`
81
+ when used in an ``await`` expression.
82
+
83
+ Examples:
84
+ Can be used to annotate the non-awaited return value of an async function:
85
+
86
+ >>> import asyncio
87
+ >>> async def divide_slowly(
88
+ ... a: float, b: float
89
+ ... ) -> Result[ZeroDivisionError, float]:
90
+ ... await asyncio.sleep(0.001)
91
+ ... try:
92
+ ... return ("success", a / b)
93
+ ... except ZeroDivisionError as e:
94
+ ... return ("failure", e)
95
+ ...
96
+ >>> async def main() -> None:
97
+ ... a_rslt: AwaitableResult[ZeroDivisionError, float] = (
98
+ ... divide_slowly(3.0, 0.0)
99
+ ... )
100
+ ... rslt: Result[ZeroDivisionError, float] = await a_rslt
101
+ ... print(rslt)
102
+ ...
103
+ >>> asyncio.run(main())
104
+ ('failure', ZeroDivisionError('float division by zero'))
105
+
106
+ Can also be used to annotate an async function:
107
+
108
+ >>> import asyncio
109
+ >>> from collections.abc import Callable
110
+ >>>
111
+ >>> async def divide_slowly(
112
+ ... a: float, b: float
113
+ ... ) -> Result[ZeroDivisionError, float]:
114
+ ... await asyncio.sleep(0.001)
115
+ ... try:
116
+ ... return ("success", a / b)
117
+ ... except ZeroDivisionError as e:
118
+ ... return ("failure", e)
119
+ ...
120
+ >>> copy_of_divide_slowly: Callable[
121
+ ... [float, float], AwaitableResult[ZeroDivisionError, float]
122
+ ... ] = divide_slowly
123
+ """
trcks/_typing.py ADDED
@@ -0,0 +1,31 @@
1
+ """Recent features from `typing`.
2
+
3
+ Imported from `typing_extensions` if necessary in older Python versions.
4
+ This helps to avoid `sys.version_info` checks in the codebase.
5
+ """
6
+
7
+ import sys
8
+
9
+ if sys.version_info >= (3, 13): # pragma: no cover
10
+ from typing import TypeVar # Argument "default" has been added in Python 3.13.
11
+ else: # pragma: no cover
12
+ from typing_extensions import TypeVar
13
+
14
+ if sys.version_info >= (3, 12): # pragma: no cover
15
+ from typing import override
16
+ else: # pragma: no cover
17
+ from typing_extensions import override
18
+
19
+ if sys.version_info >= (3, 11): # pragma: no cover
20
+ from typing import Never, TypeAlias, assert_never
21
+ else: # pragma: no cover
22
+ from typing_extensions import Never, TypeAlias, assert_never
23
+
24
+ __all__ = [
25
+ "Never",
26
+ "TypeAlias",
27
+ "TypeVar",
28
+ "assert_never",
29
+ "override",
30
+ ]
31
+ __docformat__ = "google"
trcks/fp/__init__.py ADDED
@@ -0,0 +1,64 @@
1
+ """Functional interface for `trcks`.
2
+
3
+ This package provides functions for processing values of the following types
4
+ in a functional style:
5
+
6
+ - `collections.abc.Awaitable`
7
+ - `trcks.AwaitableResult`
8
+ - `trcks.Result`
9
+
10
+ Example:
11
+ This example uses the modules `trcks.fp.composition` and `trcks.fp.monads.result`
12
+ to create and further process a value of type `trcks.Result`:
13
+
14
+ >>> import math
15
+ >>> from typing import Literal
16
+ >>> from trcks import Result
17
+ >>> from trcks.fp.composition import pipe
18
+ >>> from trcks.fp.monads import result as r
19
+ >>> GetSquareRootResult = Result[Literal["negative value"], float]
20
+ >>> def get_square_root(x: float) -> GetSquareRootResult:
21
+ ... return pipe(
22
+ ... (
23
+ ... x,
24
+ ... lambda xx:
25
+ ... ("success", xx)
26
+ ... if xx >= 0
27
+ ... else ("failure", "negative value"),
28
+ ... r.map_success(math.sqrt),
29
+ ... )
30
+ ... )
31
+ ...
32
+ >>> get_square_root(25.0)
33
+ ('success', 5.0)
34
+ >>> get_square_root(-25.0)
35
+ ('failure', 'negative value')
36
+
37
+ If your static type checker cannot infer the type of
38
+ the argument passed to `trcks.fp.composition.pipe`,
39
+ you can explicitly assign a type:
40
+
41
+ >>> import math
42
+ >>> from typing import Literal
43
+ >>> from trcks import Result
44
+ >>> from trcks.fp.composition import Pipeline2, pipe
45
+ >>> from trcks.fp.monads import result as r
46
+ >>> GetSquareRootResult = Result[Literal["negative value"], float]
47
+ >>> def get_square_root(x: float) -> GetSquareRootResult:
48
+ ... p: Pipeline2[float, GetSquareRootResult, GetSquareRootResult] = (
49
+ ... x,
50
+ ... lambda xx:
51
+ ... ("success", xx)
52
+ ... if xx >= 0
53
+ ... else ("failure", "negative value"),
54
+ ... r.map_success(math.sqrt),
55
+ ... )
56
+ ... return pipe(p)
57
+ ...
58
+ >>> get_square_root(25.0)
59
+ ('success', 5.0)
60
+ >>> get_square_root(-25.0)
61
+ ('failure', 'negative value')
62
+ """
63
+
64
+ __docformat__ = "google"
@@ -0,0 +1,268 @@
1
+ """Types and higher order functions for function composition.
2
+
3
+ Attributes:
4
+ Composable:
5
+ Up to seven compatible functions that can be applied sequentially
6
+ from first to last.
7
+ Composable1:
8
+ A single function.
9
+ Composable2:
10
+ Two compatible functions that can be applied sequentially from first to last.
11
+ Composable3:
12
+ Three compatible functions that can be applied sequentially from first to last.
13
+ Composable4:
14
+ Four compatible functions that can be applied sequentially from first to last.
15
+ Composable5:
16
+ Five compatible functions that can be applied sequentially from first to last.
17
+ Composable6:
18
+ Six compatible functions that can be applied sequentially from first to last.
19
+ Composable7:
20
+ Seven compatible functions that can be applied sequentially from first to last.
21
+ Pipeline:
22
+ A single value followed by up to seven compatible functions
23
+ that can be applied sequentially from first to last.
24
+ Pipeline0:
25
+ A single value.
26
+ Pipeline1:
27
+ A single value followed by a single compatible function
28
+ that can be applied.
29
+ Pipeline2:
30
+ A single value followed by two compatible functions
31
+ that can be applied sequentially from first to last.
32
+ Pipeline3:
33
+ A single value followed by three compatible functions
34
+ that can be applied sequentially from first to last.
35
+ Pipeline4:
36
+ A single value followed by four compatible functions
37
+ that can be applied sequentially from first to last.
38
+ Pipeline5:
39
+ A single value followed by five compatible functions
40
+ that can be applied sequentially from first to last.
41
+ Pipeline6:
42
+ A single value followed by six compatible functions
43
+ that can be applied sequentially from first to last.
44
+ Pipeline7:
45
+ A single value followed by seven compatible functions
46
+ that can be applied sequentially from first to last.
47
+
48
+ Example:
49
+ Sequentially apply two functions to one input value
50
+ in three different ways:
51
+
52
+ >>> def to_length_string(n: int) -> str:
53
+ ... return f"Length: {n}"
54
+ ...
55
+ >>> input_ = "Hello, world!"
56
+ >>> to_length_string(len(input_))
57
+ 'Length: 13'
58
+ >>> get_length_string = compose((len, to_length_string))
59
+ >>> get_length_string(input_)
60
+ 'Length: 13'
61
+ >>> pipe((input_, len, to_length_string))
62
+ 'Length: 13'
63
+ """
64
+
65
+ from __future__ import annotations
66
+
67
+ from collections.abc import Callable
68
+ from typing import Union
69
+
70
+ from trcks._typing import TypeAlias, TypeVar, assert_never
71
+
72
+ __docformat__ = "google"
73
+
74
+
75
+ _IN = TypeVar("_IN")
76
+ _OUT = TypeVar("_OUT")
77
+ _T0 = TypeVar("_T0")
78
+ _T1 = TypeVar("_T1")
79
+ _T2 = TypeVar("_T2")
80
+ _T3 = TypeVar("_T3")
81
+ _T4 = TypeVar("_T4")
82
+ _T5 = TypeVar("_T5")
83
+ _T6 = TypeVar("_T6")
84
+ _T7 = TypeVar("_T7")
85
+
86
+ # Tuple type unpacking does not work correctly in Python 3.9 and 3.10
87
+ # (see https://github.com/python/typing_extensions/issues/103).
88
+ # Therefore, the following tuple type definitions contain a lot of repetitions:
89
+
90
+ Composable1: TypeAlias = tuple[Callable[[_T0], _T1],]
91
+
92
+ Composable2: TypeAlias = tuple[
93
+ Callable[[_T0], _T1],
94
+ Callable[[_T1], _T2],
95
+ ]
96
+
97
+ Composable3: TypeAlias = tuple[
98
+ Callable[[_T0], _T1],
99
+ Callable[[_T1], _T2],
100
+ Callable[[_T2], _T3],
101
+ ]
102
+
103
+ Composable4: TypeAlias = tuple[
104
+ Callable[[_T0], _T1],
105
+ Callable[[_T1], _T2],
106
+ Callable[[_T2], _T3],
107
+ Callable[[_T3], _T4],
108
+ ]
109
+
110
+ Composable5: TypeAlias = tuple[
111
+ Callable[[_T0], _T1],
112
+ Callable[[_T1], _T2],
113
+ Callable[[_T2], _T3],
114
+ Callable[[_T3], _T4],
115
+ Callable[[_T4], _T5],
116
+ ]
117
+
118
+ Composable6: TypeAlias = tuple[
119
+ Callable[[_T0], _T1],
120
+ Callable[[_T1], _T2],
121
+ Callable[[_T2], _T3],
122
+ Callable[[_T3], _T4],
123
+ Callable[[_T4], _T5],
124
+ Callable[[_T5], _T6],
125
+ ]
126
+
127
+ Composable7: TypeAlias = tuple[
128
+ Callable[[_T0], _T1],
129
+ Callable[[_T1], _T2],
130
+ Callable[[_T2], _T3],
131
+ Callable[[_T3], _T4],
132
+ Callable[[_T4], _T5],
133
+ Callable[[_T5], _T6],
134
+ Callable[[_T6], _T7],
135
+ ]
136
+
137
+ Composable: TypeAlias = Union[
138
+ Composable7[_IN, _T1, _T2, _T3, _T4, _T5, _T6, _OUT],
139
+ Composable6[_IN, _T1, _T2, _T3, _T4, _T5, _OUT],
140
+ Composable5[_IN, _T1, _T2, _T3, _T4, _OUT],
141
+ Composable4[_IN, _T1, _T2, _T3, _OUT],
142
+ Composable3[_IN, _T1, _T2, _OUT],
143
+ Composable2[_IN, _T1, _OUT],
144
+ Composable1[_IN, _OUT],
145
+ ]
146
+
147
+ Pipeline0: TypeAlias = tuple[_T0,]
148
+
149
+ Pipeline1: TypeAlias = tuple[
150
+ _T0,
151
+ Callable[[_T0], _T1],
152
+ ]
153
+
154
+ Pipeline2: TypeAlias = tuple[
155
+ _T0,
156
+ Callable[[_T0], _T1],
157
+ Callable[[_T1], _T2],
158
+ ]
159
+
160
+ Pipeline3: TypeAlias = tuple[
161
+ _T0,
162
+ Callable[[_T0], _T1],
163
+ Callable[[_T1], _T2],
164
+ Callable[[_T2], _T3],
165
+ ]
166
+
167
+ Pipeline4: TypeAlias = tuple[
168
+ _T0,
169
+ Callable[[_T0], _T1],
170
+ Callable[[_T1], _T2],
171
+ Callable[[_T2], _T3],
172
+ Callable[[_T3], _T4],
173
+ ]
174
+
175
+ Pipeline5: TypeAlias = tuple[
176
+ _T0,
177
+ Callable[[_T0], _T1],
178
+ Callable[[_T1], _T2],
179
+ Callable[[_T2], _T3],
180
+ Callable[[_T3], _T4],
181
+ Callable[[_T4], _T5],
182
+ ]
183
+
184
+ Pipeline6: TypeAlias = tuple[
185
+ _T0,
186
+ Callable[[_T0], _T1],
187
+ Callable[[_T1], _T2],
188
+ Callable[[_T2], _T3],
189
+ Callable[[_T3], _T4],
190
+ Callable[[_T4], _T5],
191
+ Callable[[_T5], _T6],
192
+ ]
193
+
194
+ Pipeline7: TypeAlias = tuple[
195
+ _T0,
196
+ Callable[[_T0], _T1],
197
+ Callable[[_T1], _T2],
198
+ Callable[[_T2], _T3],
199
+ Callable[[_T3], _T4],
200
+ Callable[[_T4], _T5],
201
+ Callable[[_T5], _T6],
202
+ Callable[[_T6], _T7],
203
+ ]
204
+
205
+ Pipeline: TypeAlias = Union[
206
+ Pipeline7[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _OUT],
207
+ Pipeline6[_T0, _T1, _T2, _T3, _T4, _T5, _OUT],
208
+ Pipeline5[_T0, _T1, _T2, _T3, _T4, _OUT],
209
+ Pipeline4[_T0, _T1, _T2, _T3, _OUT],
210
+ Pipeline3[_T0, _T1, _T2, _OUT],
211
+ Pipeline2[_T0, _T1, _OUT],
212
+ Pipeline1[_T0, _OUT],
213
+ Pipeline0[_OUT],
214
+ ]
215
+
216
+
217
+ def compose( # noqa: PLR0911
218
+ c: Composable[_IN, _T1, _T2, _T3, _T4, _T5, _T6, _OUT],
219
+ ) -> Callable[[_IN], _OUT]:
220
+ """Compose a tuple of compatible functions from first to last.
221
+
222
+ Args:
223
+ c: Compatible functions that can be applied sequentially from first to last.
224
+
225
+ Returns:
226
+ Function that applies the given functions from first to last.
227
+
228
+ Example:
229
+ >>> get_length_string = compose((len, lambda n: f"Length: {n}"))
230
+ >>> get_length_string("Hello, world!")
231
+ 'Length: 13'
232
+ """
233
+ if len(c) == 1:
234
+ return lambda in_: c[0](in_)
235
+ if len(c) == 2: # noqa: PLR2004
236
+ return lambda in_: c[1](c[0](in_))
237
+ if len(c) == 3: # noqa: PLR2004
238
+ return lambda in_: c[2](c[1](c[0](in_)))
239
+ if len(c) == 4: # noqa: PLR2004
240
+ return lambda in_: c[3](c[2](c[1](c[0](in_))))
241
+ if len(c) == 5: # noqa: PLR2004
242
+ return lambda in_: c[4](c[3](c[2](c[1](c[0](in_)))))
243
+ if len(c) == 6: # noqa: PLR2004
244
+ return lambda in_: c[5](c[4](c[3](c[2](c[1](c[0](in_))))))
245
+ if len(c) == 7: # noqa: PLR2004
246
+ return lambda in_: c[6](c[5](c[4](c[3](c[2](c[1](c[0](in_)))))))
247
+ return assert_never(c) # type: ignore [unreachable] # pragma: no cover
248
+
249
+
250
+ def pipe(p: Pipeline[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _OUT]) -> _OUT:
251
+ """Evaluate a `Pipeline`.
252
+
253
+ Args:
254
+ p:
255
+ Single value followed by up to seven compatible functions
256
+ that can be applied sequentially from first to last.
257
+
258
+ Returns:
259
+ Result of sequentially applying the given functions from first to last
260
+ to the given value.
261
+
262
+ Example:
263
+ >>> pipe(("Hello, world!", len, lambda n: f"Length: {n}"))
264
+ 'Length: 13'
265
+ """
266
+ if len(p) == 1:
267
+ return p[0]
268
+ return compose(p[1:])(p[0])
@@ -0,0 +1,3 @@
1
+ """Monadic functions for generic types."""
2
+
3
+ __docformat__ = "google"
@@ -0,0 +1,182 @@
1
+ """Monadic functions for `collections.abc.Awaitable`.
2
+
3
+ Provides utilities for functional composition of asynchronous functions.
4
+
5
+ Example:
6
+ >>> import asyncio
7
+ >>> from trcks.fp.composition import pipe
8
+ >>> from trcks.fp.monads import awaitable as a
9
+ >>> async def read_from_disk() -> str:
10
+ ... await asyncio.sleep(0.001)
11
+ ... input_ = "Hello, world!"
12
+ ... print(f"Read '{input_}' from disk.")
13
+ ... return input_
14
+ ...
15
+ >>> def transform(s: str) -> str:
16
+ ... return f"Length: {len(s)}"
17
+ ...
18
+ >>> async def write_to_disk(output: str) -> None:
19
+ ... await asyncio.sleep(0.001)
20
+ ... print(f"Wrote '{output}' to disk.")
21
+ ...
22
+ >>> async def main() -> None:
23
+ ... awaitable_str = read_from_disk()
24
+ ... return await pipe(
25
+ ... (awaitable_str, a.map_(transform), a.map_to_awaitable(write_to_disk))
26
+ ... )
27
+ ...
28
+ >>> asyncio.run(main())
29
+ Read 'Hello, world!' from disk.
30
+ Wrote 'Length: 13' to disk.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ from typing import TYPE_CHECKING
36
+
37
+ from trcks._typing import TypeVar
38
+
39
+ if TYPE_CHECKING: # pragma: no cover
40
+ from collections.abc import Awaitable, Callable
41
+
42
+ __docformat__ = "google"
43
+
44
+ _T = TypeVar("_T")
45
+ _T1 = TypeVar("_T1")
46
+ _T2 = TypeVar("_T2")
47
+
48
+
49
+ async def _construct(value: _T) -> _T:
50
+ return value
51
+
52
+
53
+ def construct(value: _T) -> Awaitable[_T]:
54
+ """Create an `Awaitable` from a value.
55
+
56
+ Args:
57
+ value: The value to create the `Awaitable` from.
58
+
59
+ Returns:
60
+ The `Awaitable` created from the value.
61
+
62
+ Example:
63
+ >>> import asyncio
64
+ >>> from collections.abc import Awaitable
65
+ >>> from trcks.fp.monads import awaitable as a
66
+ >>> awtbl = a.construct("Hello, world!")
67
+ >>> isinstance(awtbl, Awaitable)
68
+ True
69
+ >>> asyncio.run(a.to_coroutine(awtbl))
70
+ 'Hello, world!'
71
+ """
72
+ return _construct(value)
73
+
74
+
75
+ def map_(f: Callable[[_T1], _T2]) -> Callable[[Awaitable[_T1]], Awaitable[_T2]]:
76
+ """Turn synchronous function into function expecting and returning `Awaitable`.
77
+
78
+ Args:
79
+ f:
80
+ The synchronous function to be transformed into
81
+ a function expecting and returning an `Awaitable`.
82
+
83
+ Returns:
84
+ The given function transformed into
85
+ a function expecting and returning an `Awaitable`.
86
+
87
+ Note:
88
+ The underscore in the function name helps to avoid collisions
89
+ with the built-in function `map`.
90
+
91
+ Example:
92
+ >>> import asyncio
93
+ >>> from collections.abc import Awaitable
94
+ >>> from trcks.fp.monads import awaitable as a
95
+ >>> def transform(s: str) -> str:
96
+ ... return f"Length: {len(s)}"
97
+ ...
98
+ >>> transform_mapped = a.map_(transform)
99
+ >>> awaitable_input = a.construct("Hello, world!")
100
+ >>> awaitable_output = transform_mapped(awaitable_input)
101
+ >>> isinstance(awaitable_output, Awaitable)
102
+ True
103
+ >>> asyncio.run(a.to_coroutine(awaitable_output))
104
+ 'Length: 13'
105
+
106
+ """
107
+
108
+ def composed_f(value: _T1) -> Awaitable[_T2]:
109
+ return construct(f(value))
110
+
111
+ return map_to_awaitable(composed_f)
112
+
113
+
114
+ def map_to_awaitable(
115
+ f: Callable[[_T1], Awaitable[_T2]],
116
+ ) -> Callable[[Awaitable[_T1]], Awaitable[_T2]]:
117
+ """Turn `Awaitable`-returning func. into func. expecting and returning `Awaitable`.
118
+
119
+ Args:
120
+ f:
121
+ The `Awaitable`-returning function to be transformed into
122
+ a function expecting and returning an `Awaitable`.
123
+
124
+ Returns:
125
+ The given function transformed into
126
+ a function expecting and returning an `Awaitable`.
127
+
128
+
129
+ Example:
130
+ >>> import asyncio
131
+ >>> from collections.abc import Awaitable
132
+ >>> from trcks.fp.monads import awaitable as a
133
+ >>> async def write_to_disk(output: str) -> None:
134
+ ... await asyncio.sleep(0.001)
135
+ ... print(f"Wrote '{output}' to disk.")
136
+ ...
137
+ >>> write_to_disk_mapped = a.map_to_awaitable(write_to_disk)
138
+ >>> awaitable_input = a.construct("Hello, world!")
139
+ >>> awaitable_output = write_to_disk_mapped(awaitable_input)
140
+ >>> isinstance(awaitable_output, Awaitable)
141
+ True
142
+ >>> asyncio.run(a.to_coroutine(awaitable_output))
143
+ Wrote 'Hello, world!' to disk.
144
+ """
145
+
146
+ async def mapped_f(awaitable: Awaitable[_T1]) -> _T2:
147
+ return await f(await awaitable)
148
+
149
+ return mapped_f
150
+
151
+
152
+ async def to_coroutine(awtbl: Awaitable[_T]) -> _T:
153
+ """Turn an `Awaitable` into a `collections.abc.Coroutine`.
154
+
155
+ This is useful for functions that expect a coroutine (e.g. `asyncio.run`).
156
+
157
+ Args:
158
+ awtbl: The `Awaitable` to be transformed into a `collections.abc.Coroutine`.
159
+
160
+ Returns:
161
+ The given `Awaitable` transformed into a `collections.abc.Coroutine`.
162
+
163
+ Note:
164
+ The type `Awaitable` is a supertype of `collections.abc.Coroutine`.
165
+
166
+ Example:
167
+ Transform an `asyncio.Future` into a `collections.abc.Coroutine` and run it:
168
+
169
+ >>> import asyncio
170
+ >>> from trcks.fp.monads import awaitable as a
171
+ >>> asyncio.set_event_loop(asyncio.new_event_loop())
172
+ >>> future = asyncio.Future[str]()
173
+ >>> future.set_result("Hello, world!")
174
+ >>> future
175
+ <Future finished result='Hello, world!'>
176
+ >>> coro = a.to_coroutine(future)
177
+ >>> coro
178
+ <coroutine object to_coroutine at 0x...>
179
+ >>> asyncio.run(coro)
180
+ 'Hello, world!'
181
+ """
182
+ return await awtbl