Coverage for /home/ubuntu/yoneda/python/yoneda/monad.py: 100%

116 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-08 16:26 +0000

1from typing import Any, Callable, Generic, Type, TypeVar, Union # noqa: F401 

2 

3from functools import partial 

4 

5from lunchbox.enforce import Enforce, EnforceError 

6import infix 

7 

8A = TypeVar('A') 

9B = TypeVar('B') 

10C = TypeVar('C') 

11# ------------------------------------------------------------------------------ 

12 

13 

14''' 

15Monad is a library containing the Monad class and a library of monadic 

16functions it calls. 

17 

18Haskell equivalence table: 

19 

20 =========== ======== ====== ===== ================ ================================ 

21 **Python** **Haskell** **Haskell Type Signature** 

22 -------------------- ------------ ------------------------------------------------- 

23 prefix infix prefix infix implication signature 

24 =========== ======== ====== ===== ================ ================================ 

25 app │iapp│ <*> Applicative f => f (a -> b) -> fa -> fb 

26 bind │ibind│ >>= Monad m => m a -> (a -> m b) -> m b 

27 fail │ifail│ fail Monad m => String -> m a 

28 fmap │ifmap│ fmap <$> Functor f => (a -> b) -> fa -> fb 

29 right │iright│ >> Monad m => m a -> m b -> m b 

30 unwrap Monad m => m a -> a 

31 wrap │iwrap│ pure Applicative f => a -> f a 

32 wrap │iwrap│ return Monad m => a -> m a 

33 curry │icurry│ 

34 dot │idot│ . . (b -> c) -> (a -> b) -> (a -> c) 

35 partial_dot . . (b -> c) -> (a -> b) -> (a -> c) 

36 =========== ======== ====== ===== ================ ================================ 

37''' 

38 

39 

40def enforce_monad(item): 

41 # type: (Any) -> None 

42 ''' 

43 Enforces item being a Monad subclass or instance. 

44 

45 Args: 

46 item (object): Item to be tested. 

47 

48 Raises: 

49 EnforceError: If item is not Monad subclass or instance. 

50 ''' 

51 pred = isinstance # type: Any 

52 if item.__class__ is type: 

53 pred = issubclass 

54 if not pred(item, Monad): 

55 raise EnforceError(f'{item} is not a subclass or instance of Monad.') 

56 

57 

58@infix.or_infix 

59def iwrap(*args, **kwargs): 

60 return wrap(*args, **kwargs) 

61 

62 

63def wrap(monad, data): 

64 # type: (Monadlike, A) -> Monad[A] 

65 ''' 

66 Wrap: M -> A -> MA 

67 

68 .. image:: images/wrap.png 

69 

70 Given a Monad class or instance, create a new Monad with given data. 

71 

72 Args: 

73 monad (Monad): Monad class or instance. 

74 data (Any): Data to be wrapped as Monad. 

75 

76 Raises: 

77 EnforceError: If monad is not Monad subclass or instance. 

78 

79 Returns: 

80 Monad[A]: Monad of data. 

81 ''' 

82 enforce_monad(monad) 

83 return monad.wrap(data) 

84 

85 

86def unwrap(monad): 

87 # type: (Monad[A]) -> A 

88 ''' 

89 Unwrap: MA -> A 

90 

91 .. image:: images/unwrap.png 

92 

93 Return the data of a given Monad instance. 

94 

95 Args: 

96 monad (Monad): Monad instance. 

97 

98 Raises: 

99 EnforceError: If monad is not Monad subclass or instance. 

100 

101 Returns: 

102 A: Monad data. 

103 ''' 

104 enforce_monad(monad) 

105 return monad._data 

106 

107 

108@infix.or_infix 

109def ifmap(*args, **kwargs): 

110 return fmap(*args, **kwargs) 

111 

112 

113def fmap(func, monad): 

114 # type: (Callable[[A], B], Monad[A]) -> Monad[B] 

115 ''' 

116 Functor map: (A -> B) -> MA -> MB 

117 

118 .. image:: images/fmap.png 

119 

120 Given a Monad of A (MA) and a function A to B, return a Monad of B (MB). 

121 

122 Args: 

123 func (function): Function (A -> B). 

124 monad (Monad): Monad of A. 

125 

126 Raises: 

127 EnforceError: If monad is not Monad subclass or instance. 

128 

129 Returns: 

130 Monad[B]: Monad of B. 

131 ''' 

132 enforce_monad(monad) 

133 return wrap(monad, func(unwrap(monad))) 

134 

135 

136@infix.or_infix 

137def iapp(*args, **kwargs): 

138 return app(*args, **kwargs) 

139 

140 

141def app(monad_func, monad): 

142 # type: (Monad[Callable[[A], B]], Monad[A]) -> Monad[B] 

143 ''' 

144 Applicative: M(A -> B) -> MA -> MB 

145 

146 .. image:: images/app.png 

147 

148 Given a Monad of A (MA) and a Monad of a function A to B, return a Monad 

149 of B (MB). 

150 

151 Args: 

152 monad_func (Monad): Monad of function (A -> B). 

153 monad (Monad): Monad of A. 

154 

155 Raises: 

156 EnforceError: If monad_func is not instance of Monad. 

157 EnforceError: If monad is not Monad subclass or instance. 

158 

159 Returns: 

160 Monad[B]: Monad of B. 

161 ''' 

162 enforce_monad(monad_func) 

163 enforce_monad(monad) 

164 func = unwrap(monad_func) 

165 value = unwrap(monad) 

166 return wrap(monad, func(value)) 

167 

168 

169@infix.or_infix 

170def ibind(*args, **kwargs): 

171 return bind(*args, **kwargs) 

172 

173 

174def bind(func, monad): 

175 # type: (Callable[[A], Monad[B]], Monad[A]) -> Monad[B] 

176 ''' 

177 Bind: (A -> MB) -> MA -> MB 

178 

179 .. image:: images/bind.png 

180 

181 Given a Monad of A (MA) and a function A to MB, return a Monad of B (MB). 

182 

183 Args: 

184 func (function): Function (A -> MB). 

185 monad (Monad): Monad of A. 

186 

187 Raises: 

188 EnforceError: If monad is not Monad subclass or instance. 

189 

190 Returns: 

191 Monad[B]: Monad of B. 

192 ''' 

193 enforce_monad(monad) 

194 return func(unwrap(monad)) 

195 

196 

197@infix.or_infix 

198def iright(*args, **kwargs): 

199 return right(*args, **kwargs) 

200 

201 

202def right(monad_a, monad_b): 

203 # type: (Monad[A], Monad[B]) -> Monad[B] 

204 ''' 

205 Right: MA -> MB -> MB 

206 

207 .. image:: images/right.png 

208 

209 Given two Monads, a and b, return the right Monad b. 

210 

211 Args: 

212 monad_a (Monad): Left monad. 

213 monad_b (Monad): Right monad. 

214 

215 Raises: 

216 EnforceError: If monad is not Monad subclass or instance. 

217 

218 Returns: 

219 Monad: Right Monad. 

220 ''' 

221 enforce_monad(monad_a) 

222 enforce_monad(monad_b) 

223 return monad_b 

224 

225 

226@infix.or_infix 

227def ifail(*args, **kwargs): 

228 return fail(*args, **kwargs) 

229 

230 

231def fail(monad, error): 

232 # type (Monad, Exception) -> Monad[Exception] 

233 ''' 

234 Fail: M -> E -> ME 

235 

236 .. image:: images/fail.png 

237 

238 Given a Monad and Exception, return a Monad of that Exception. 

239 

240 Args: 

241 monad (Monad): Monad to wrap error with. 

242 error (Exception): Error. 

243 

244 Raises: 

245 EnforceError: If monad is not Monad subclass or instance. 

246 EnforceError: If error is not an instance of Exception. 

247 

248 Returns: 

249 Monad: Error Monad. 

250 ''' 

251 enforce_monad(monad) 

252 msg = 'Error must be an instance of Exception. Given value: {a}.' 

253 Enforce(error, 'instance of', Exception, message=msg) 

254 return wrap(monad, error) 

255 

256 

257def succeed(monad, value): 

258 # type (Monad, A) -> Monad[A] 

259 ''' 

260 Succed: M -> A -> MA 

261 

262 .. image:: images/wrap.png 

263 

264 Given a Monad and a value, return a Monad of that value. 

265 

266 Args: 

267 monad (Monad): Monad to wrap value with. 

268 value (object): Value. 

269 

270 Raises: 

271 EnforceError: If monad is not Monad subclass or instance. 

272 EnforceError: If value is an instance of Exception. 

273 

274 Returns: 

275 Monad: Monad of value. 

276 ''' 

277 enforce_monad(monad) 

278 msg = 'Error must not be an instance of Exception. Given value: {a}.' 

279 Enforce(value, 'not instance of', Exception, message=msg) 

280 return wrap(monad, value) 

281 

282 

283@infix.or_infix 

284def icurry(*args, **kwargs): 

285 return curry(*args, **kwargs) 

286 

287 

288def curry(func, *args, **kwargs): 

289 # type: (Callable, Any, Any) -> Callable 

290 ''' 

291 Infix notation for functools.partial. 

292 

293 Args: 

294 func (function): Function to be curried. 

295 args (optional): Arguments. 

296 kwargs (optional): Keyword arguments. 

297 

298 Returns: 

299 function: Curried function. 

300 ''' 

301 return partial(func, *args, **kwargs) 

302 

303 

304@infix.or_infix 

305def idot(*args, **kwargs): 

306 return dot(*args, **kwargs) 

307 

308 

309def dot(func_b, func_a): 

310 # type: (Callable[[B], C], Callable[[A], B]) -> Callable[[A], C] 

311 ''' 

312 Dot: (b -> c) -> (a -> b) -> (a -> c) 

313 fb |idot| fa == fb(fa) 

314 

315 Composes two functions. 

316 

317 Example: 

318 ``` 

319 fa = lambda x: x + 'a' 

320 fb = lambda x: x + 'b' 

321 dot(fb, fa)('x') == 'xab' 

322 (fb | idot | fa)('x') == 'xab' 

323 ``` 

324 

325 Args: 

326 func_b (function): Outer function. 

327 func_a (function): Inner function. 

328 

329 Returns: 

330 partial: Function composition. 

331 ''' 

332 

333 def of(b, a, *args, **kwargs): 

334 return b(a(*args, **kwargs)) 

335 

336 return partial(of, func_b, func_a) 

337 

338 

339def partial_dot(func): 

340 # type: (Callable[[B], C]) -> partial[Callable[[A], B]] 

341 ''' 

342 Partial Dot: (b -> c) -> (a -> b) 

343 

344 Partial version of dot function. 

345 

346 Example: 

347 ``` 

348 app = ym.app 

349 u = Monad(lambda x: x + 1) 

350 v = Monad(lambda x: x + 2) 

351 w = Monad(3) 

352 Monad(partial_dot) | iapp | u | iapp | v | iapp | w 

353 ``` 

354 

355 Args: 

356 func (function): Outer composition function. 

357 

358 Returns: 

359 partial: Function composition. 

360 ''' 

361 return partial(dot, func) 

362 

363 

364def catch(monad, func): 

365 # type: (MA, Callable[[A], B]) -> Callable[[A], Union[B, Exception]] 

366 ''' 

367 Catch: MA -> (A -> B) -> (MB | ME) 

368 

369 Catches exception and returns it rather then raising an error. 

370 

371 Args: 

372 monad (Monad): Monad. 

373 func (function): Function to attempt. 

374 

375 Raises: 

376 EnforceError: If monad is not Monad subclass or instance. 

377 

378 Returns: 

379 object: Partial function with catch logic. 

380 ''' 

381 enforce_monad(monad) 

382 

383 def catch_(func, *args, **kwargs): 

384 try: 

385 return func(*args, **kwargs) 

386 except Exception as error: 

387 return fail(monad, error) 

388 

389 return partial(catch_, func) 

390 

391 

392# ------------------------------------------------------------------------------ 

393 

394 

395class Monad(Generic[A]): 

396 ''' 

397 Monad is a generic base class for monads. It implements all the monad 

398 functions as methods which take itself as the first argument. 

399 

400 Haskell equivalence table: 

401 

402 ====== ===== ====== ===== ================ ======================== 

403 **Python** **Haskell** **Haskell Type Signature** 

404 ------------ ------------ ----------------------------------------- 

405 prefix infix prefix infix implication signature 

406 ====== ===== ====== ===== ================ ======================== 

407 app ^ <*> Applicative f => f (a -> b) -> fa -> fb 

408 bind >> >>= Monad m => m a -> (a -> m b) -> m b 

409 fail fail Monad m => String -> m a 

410 fmap & fmap <$> Functor f => (a -> b) -> fa -> fb 

411 right >> Monad m => m a -> m b -> m b 

412 unwrap Monad m => m a -> a 

413 wrap pure Applicative f => a -> f a 

414 wrap return Monad m => a -> m a 

415 ====== ===== ====== ===== ================ ======================== 

416 ''' 

417 

418 def __init__(self, data): 

419 # type: (A) -> None 

420 ''' 

421 Constructs monad instance. 

422 

423 Args: 

424 data (object): Data to be wrapped with Monad. 

425 ''' 

426 self._data = data 

427 

428 def __repr__(self): 

429 # type: () -> str 

430 ''' 

431 String representation of Monad instance. 

432 ''' 

433 return f'{self.__class__.__name__}({self._data.__repr__()})' 

434 

435 @classmethod 

436 def wrap(cls, data): 

437 # type: (A) -> MA 

438 ''' 

439 Wrap: A -> MA 

440 

441 Create a new Monad with given data. 

442 

443 Args: 

444 data (Any): Data to be wrapped as Monad. 

445 

446 Returns: 

447 Monad[A]: Monad of data. 

448 ''' 

449 return cls(data) 

450 

451 def unwrap(self): 

452 # type: () -> A 

453 ''' 

454 Unwrap: () -> A 

455 

456 Return self._data. 

457 

458 Returns: 

459 A: Monad data. 

460 ''' 

461 return unwrap(self) 

462 

463 def fmap(self, func): 

464 # type: (Callable[[A], B]) -> MB 

465 ''' 

466 Functor map: (A -> B) -> MB 

467 

468 Given a function A to B, return a Monad of B (MB). 

469 Example: m.fmap(lambda x: x + 2) 

470 

471 Args: 

472 func (function): Function (A -> B). 

473 

474 Returns: 

475 Monad[B]: Monad of B. 

476 ''' 

477 return fmap(func, self) 

478 

479 def app(self, monad_func): 

480 # type: (Monad[Callable[[A], B]]) -> MB 

481 ''' 

482 Applicative: M(A -> B) -> MB 

483 

484 Given a Monad of a function A to B, return a Monad of B (MB). 

485 

486 Args: 

487 monad_func (Monad): Monad of function (A -> B). 

488 

489 Returns: 

490 Monad[B]: Monad of B. 

491 ''' 

492 return app(monad_func, self) 

493 

494 def bind(self, func): 

495 # type: (Callable[[A], MB]) -> MB 

496 ''' 

497 Bind: (A -> MB) -> MB 

498 

499 Given a function A to MB, return a Monad of B (MB). 

500 

501 Args: 

502 func (function): Function (A -> MB). 

503 

504 Returns: 

505 Monad[B]: Monad of B. 

506 ''' 

507 return bind(func, self) 

508 

509 def right(self, monad): 

510 # type: (MB) -> MB 

511 ''' 

512 Right: MB -> MB 

513 

514 Return given monad (self is left, given monad is right). 

515 

516 Args: 

517 monad (Monad): Right monad. 

518 

519 Returns: 

520 Monad: Right Monad. 

521 ''' 

522 return right(self, monad) 

523 

524 def fail(self, error): 

525 # type (Exception) -> Monad[Exception] 

526 ''' 

527 Fail: E -> ME 

528 

529 Return a Monad of given Exception. 

530 

531 Args: 

532 error (Exception): Error. 

533 

534 Returns: 

535 Monad: Error Monad. 

536 ''' 

537 return fail(self, error) 

538 

539 def __and__(self, func): 

540 # type: (Callable[[A], B]) -> MB 

541 ''' 

542 Functor map: (A -> B) -> MB 

543 

544 Given a function A to B, return a Monad of B (MB). 

545 Example: m & (lambda x: x + 2) 

546 

547 Args: 

548 func (function): Function (A -> B). 

549 

550 Returns: 

551 Monad[B]: Monad of B. 

552 ''' 

553 return self.fmap(func) 

554 

555 def __xor__(self, monad_func): 

556 # type: (MA, Monad[Callable[[A], B]]) -> MB 

557 ''' 

558 Applicative: MA -> M(A -> B) -> MB 

559 

560 .. image:: images/app.png 

561 

562 Given a Monad of A (MA) and a Monad of a function A to B, return a Monad 

563 of B (MB). 

564 Example: m ^ Monad.wrap(lambda x: x + 2) 

565 

566 Args: 

567 monad (Monad): Monad of A. 

568 func (Monad): Monad of function (A -> B). 

569 

570 Raises: 

571 EnforceError: If monad is not Monad subclass or instance. 

572 

573 Returns: 

574 Monad[B]: Monad of B. 

575 ''' 

576 return self.app(monad_func) 

577 

578 def __rshift__(self, func): 

579 # type: (Callable[[A], MB]) -> MB 

580 ''' 

581 Bind: (A -> MB) -> MB 

582 

583 Given a function A to MB, return a Monad of B (MB). 

584 Example: m >> Monad 

585 

586 Args: 

587 func (function): Function (A -> MB). 

588 

589 Returns: 

590 Monad[B]: Monad of B. 

591 ''' 

592 return self.bind(func) 

593 

594 

595Monadlike = Union[Monad, Type[Monad]] 

596MA = Monad[A] 

597MB = Monad[B] 

598MC = Monad[C]