functorial 0.1.1__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.
- functorial/__about__.py +4 -0
- functorial/__init__.py +29 -0
- functorial/all.py +54 -0
- functorial/alternative.py +36 -0
- functorial/applicative.py +168 -0
- functorial/backwards.py +18 -0
- functorial/bicofunctor.py +48 -0
- functorial/bifunctor.py +52 -0
- functorial/bitraversable.py +41 -0
- functorial/cofunctor.py +70 -0
- functorial/const.py +199 -0
- functorial/dev/__init__.py +1 -0
- functorial/dev/phases.py +93 -0
- functorial/dict.py +76 -0
- functorial/either.py +165 -0
- functorial/examples/__init__.py +1 -0
- functorial/examples/divide_conquer.py +71 -0
- functorial/examples/huffman.py +208 -0
- functorial/foldable.py +227 -0
- functorial/functions.py +419 -0
- functorial/functor.py +89 -0
- functorial/identity.py +55 -0
- functorial/io.py +161 -0
- functorial/lazy.py +47 -0
- functorial/list.py +382 -0
- functorial/maybe.py +204 -0
- functorial/monad.py +126 -0
- functorial/monoids.py +524 -0
- functorial/ntuple.py +131 -0
- functorial/ops.py +44 -0
- functorial/optics/__init__.py +23 -0
- functorial/optics/affine_fold.py +106 -0
- functorial/optics/affine_traversal.py +122 -0
- functorial/optics/all.py +26 -0
- functorial/optics/choice.py +93 -0
- functorial/optics/cochoice.py +54 -0
- functorial/optics/costrong.py +46 -0
- functorial/optics/deprecated/cartesian.py +65 -0
- functorial/optics/deprecated/forget.py +136 -0
- functorial/optics/deprecated/star.py +76 -0
- functorial/optics/fold.py +120 -0
- functorial/optics/generics.py +96 -0
- functorial/optics/getter.py +48 -0
- functorial/optics/iso.py +159 -0
- functorial/optics/ix_fold.py +133 -0
- functorial/optics/ix_lens.py +202 -0
- functorial/optics/ix_traversal.py +128 -0
- functorial/optics/lens.py +178 -0
- functorial/optics/make_lens.py +139 -0
- functorial/optics/optic.py +413 -0
- functorial/optics/prism.py +113 -0
- functorial/optics/profunctors.py +678 -0
- functorial/optics/re_.py +129 -0
- functorial/optics/review.py +156 -0
- functorial/optics/setter.py +64 -0
- functorial/optics/strong.py +75 -0
- functorial/optics/traversal.py +93 -0
- functorial/optics/vl_optics.py +462 -0
- functorial/pair.py +101 -0
- functorial/profunctor.py +61 -0
- functorial/py.typed +0 -0
- functorial/reader.py +94 -0
- functorial/set.py +45 -0
- functorial/singleton.py +214 -0
- functorial/state.py +110 -0
- functorial/traversable.py +112 -0
- functorial/trees.py +820 -0
- functorial/unit.py +10 -0
- functorial/utils.py +137 -0
- functorial/wrappers.py +52 -0
- functorial/writer.py +127 -0
- functorial-0.1.1.dist-info/METADATA +68 -0
- functorial-0.1.1.dist-info/RECORD +75 -0
- functorial-0.1.1.dist-info/WHEEL +4 -0
- functorial-0.1.1.dist-info/licenses/LICENSE +21 -0
functorial/__about__.py
ADDED
functorial/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'applicative',
|
|
3
|
+
'bicofunctor',
|
|
4
|
+
'bifunctor',
|
|
5
|
+
'bitraversable',
|
|
6
|
+
'cofunctor',
|
|
7
|
+
'const',
|
|
8
|
+
'dict',
|
|
9
|
+
'either',
|
|
10
|
+
'foldable',
|
|
11
|
+
'functor',
|
|
12
|
+
'identity',
|
|
13
|
+
'list',
|
|
14
|
+
'maybe',
|
|
15
|
+
'monad',
|
|
16
|
+
'monoids',
|
|
17
|
+
'ntuple',
|
|
18
|
+
'pair',
|
|
19
|
+
'profunctor',
|
|
20
|
+
'reader',
|
|
21
|
+
'set',
|
|
22
|
+
'state',
|
|
23
|
+
'traversable',
|
|
24
|
+
'trees',
|
|
25
|
+
'functions',
|
|
26
|
+
'ops',
|
|
27
|
+
'optics',
|
|
28
|
+
'utils',
|
|
29
|
+
]
|
functorial/all.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Helper module to load all the names into the namespace
|
|
3
|
+
#
|
|
4
|
+
# Do 'from FP.all import *' to load all the needed objects.
|
|
5
|
+
#
|
|
6
|
+
# ruff: noqa: F401, F403, F405
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from . import monoids
|
|
11
|
+
from . import optics
|
|
12
|
+
|
|
13
|
+
from .alternative import *
|
|
14
|
+
from .applicative import *
|
|
15
|
+
from .bicofunctor import *
|
|
16
|
+
from .bifunctor import *
|
|
17
|
+
from .cofunctor import *
|
|
18
|
+
from .foldable import *
|
|
19
|
+
from .functor import *
|
|
20
|
+
from .monad import *
|
|
21
|
+
from .profunctor import *
|
|
22
|
+
from .traversable import *
|
|
23
|
+
|
|
24
|
+
from .const import *
|
|
25
|
+
from .either import *
|
|
26
|
+
from .identity import *
|
|
27
|
+
from .list import *
|
|
28
|
+
from .maybe import *
|
|
29
|
+
from .ntuple import *
|
|
30
|
+
from .pair import *
|
|
31
|
+
|
|
32
|
+
from .dict import *
|
|
33
|
+
from .monoids import Monoid, munit, mcombine
|
|
34
|
+
from .reader import *
|
|
35
|
+
from .set import *
|
|
36
|
+
from .state import *
|
|
37
|
+
from .trees import *
|
|
38
|
+
from .writer import *
|
|
39
|
+
|
|
40
|
+
from .functions import *
|
|
41
|
+
from .io import *
|
|
42
|
+
from .ops import *
|
|
43
|
+
from .utils import *
|
|
44
|
+
from .wrappers import *
|
|
45
|
+
|
|
46
|
+
from .pair import pair # More powerful version over .functions.pair
|
|
47
|
+
|
|
48
|
+
from .optics.all import *
|
|
49
|
+
|
|
50
|
+
#
|
|
51
|
+
# Conveniences
|
|
52
|
+
#
|
|
53
|
+
|
|
54
|
+
c = compose
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# trait Applicative f => Alternative (f : Type -> Type) where
|
|
2
|
+
# empty : f a
|
|
3
|
+
# alt : f a -> f a -> f a
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from abc import abstractmethod
|
|
8
|
+
from typing import Protocol
|
|
9
|
+
|
|
10
|
+
from .applicative import Applicative
|
|
11
|
+
|
|
12
|
+
__all__ = ['Alternative', 'alt', 'guard']
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
#
|
|
16
|
+
# Alternative as a mixin
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
# ATTN: Should this have a type parameter or be handled like Applicative?
|
|
20
|
+
|
|
21
|
+
class Alternative[A](Applicative, Protocol):
|
|
22
|
+
@classmethod
|
|
23
|
+
def empty(cls) -> Alternative[A]:
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def alt(self, fb: Alternative[A]) -> Alternative[A]:
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
def alt[A](fa: Alternative[A], fb: Alternative[A]) -> Alternative[A]:
|
|
31
|
+
return fa.alt(fb)
|
|
32
|
+
|
|
33
|
+
def guard(f: type[Alternative], condition: bool) -> Alternative[tuple[()]]: # ATTN: type Unit = tuple[()]
|
|
34
|
+
return f.unit() if condition else f.empty()
|
|
35
|
+
|
|
36
|
+
# ATTN: Include some and many? Can we implement them?
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# trait Functor f => Applicative (f : Type -> Type) where
|
|
2
|
+
# pure : a -> f a
|
|
3
|
+
# map2 : (a -> b -> c) -> f a -> f b -> f c -- lift2 := map2 h
|
|
4
|
+
# ap : f (a -> b) -> f a -> f b
|
|
5
|
+
#
|
|
6
|
+
# unit : f Unit -- Unit equiv ()
|
|
7
|
+
# combine : f a -> f b -> f (a, b)
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from abc import abstractmethod
|
|
12
|
+
from collections.abc import Callable
|
|
13
|
+
from typing import Protocol, runtime_checkable
|
|
14
|
+
|
|
15
|
+
from .functor import Functor, map # pylint: disable=redefined-builtin
|
|
16
|
+
from .functions import compose, const, curry, identity, pair, fn_eval, eval_with
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
'Applicative', 'map2', 'combine', 'pure',
|
|
20
|
+
'ap', 'lift2', 'ap_first', 'ap_second', 'rev_ap',
|
|
21
|
+
'when', 'unless', # ATTN: needed?
|
|
22
|
+
'IdentityA',
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
#
|
|
27
|
+
# Applicative as a mixin
|
|
28
|
+
#
|
|
29
|
+
|
|
30
|
+
@runtime_checkable
|
|
31
|
+
class Applicative(Functor, Protocol):
|
|
32
|
+
@classmethod
|
|
33
|
+
def pure(cls, a):
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def map2(self, g, fb):
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def unit(cls):
|
|
42
|
+
return cls.pure( () )
|
|
43
|
+
|
|
44
|
+
def combine(self, fb):
|
|
45
|
+
return self.map2(pair, fb)
|
|
46
|
+
|
|
47
|
+
def ap(self, fb):
|
|
48
|
+
return self.map2(fn_eval, fb)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def map2(g, fa, fb):
|
|
52
|
+
return fa.map2(g, fb)
|
|
53
|
+
|
|
54
|
+
def combine(fa, fb):
|
|
55
|
+
return fa.combine(fb)
|
|
56
|
+
|
|
57
|
+
def pure(fa, a):
|
|
58
|
+
return fa.pure(a)
|
|
59
|
+
|
|
60
|
+
def ap(fa_to_b: Applicative | Callable, fa: Applicative, *fs: Applicative, auto_curry=True) -> Applicative:
|
|
61
|
+
if not isinstance(fa_to_b, Applicative):
|
|
62
|
+
if auto_curry:
|
|
63
|
+
fa_to_b = fa.pure(curry(fa_to_b))
|
|
64
|
+
else:
|
|
65
|
+
fa_to_b = fa.pure(fa_to_b)
|
|
66
|
+
# elif auto_curry:
|
|
67
|
+
# fa_to_b = fa_to_b.map(curry) # ATTN: PROVISIONAL
|
|
68
|
+
|
|
69
|
+
fb = fa_to_b.ap(fa) # type: ignore
|
|
70
|
+
for fx in fs:
|
|
71
|
+
fb = fb.ap(fx)
|
|
72
|
+
return fb
|
|
73
|
+
|
|
74
|
+
# ATTN: by our emerging convention, this should be called map2_, though this name is good too
|
|
75
|
+
def lift2[A, B, C](f: Callable[[A, B], C]):
|
|
76
|
+
"""Lifts a two-argument function to a mapping of Applicatives.
|
|
77
|
+
|
|
78
|
+
This is just the partial application map2(f, _, __).
|
|
79
|
+
The applicatives should be the same type (technically
|
|
80
|
+
one should be a subclass of the other).
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
def liftA2(fa: Applicative, fb: Applicative) -> Applicative:
|
|
84
|
+
if not issubclass(fa.__class__, fb.__class__) and not issubclass(fb.__class__, fa.__class__):
|
|
85
|
+
raise TypeError('lift2(f) should be applied to compatible applicatives.')
|
|
86
|
+
return fa.map2(f, fb)
|
|
87
|
+
|
|
88
|
+
return liftA2
|
|
89
|
+
|
|
90
|
+
map2_ = lift2 # Alias for lift2 that matches our naming convention
|
|
91
|
+
|
|
92
|
+
def rev_ap(fa: Applicative, fa_to_b: Applicative | Callable, auto_curry=True) -> Applicative:
|
|
93
|
+
"""A variant of ap with the arguments reversed and effects resolved in the order given.
|
|
94
|
+
|
|
95
|
+
Note that rev_ap differs from flip(ap) in the order in which effects
|
|
96
|
+
are resolved. The latter would just remap the argument order into ap,
|
|
97
|
+
but this resolves fa then fa_to_b.
|
|
98
|
+
|
|
99
|
+
Unlike ap, this only takes two arguments, but it does do automatic currying
|
|
100
|
+
if auto_curry is True, which is the default.
|
|
101
|
+
|
|
102
|
+
Returns the resulting applicative.
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
if not isinstance(fa_to_b, Applicative):
|
|
106
|
+
if auto_curry:
|
|
107
|
+
fa_to_b = fa.pure(curry(fa_to_b))
|
|
108
|
+
else:
|
|
109
|
+
fa_to_b = fa.pure(fa_to_b)
|
|
110
|
+
|
|
111
|
+
return fa.map2(eval_with, fa_to_b)
|
|
112
|
+
|
|
113
|
+
# (<*) : f a -> f b -> f a
|
|
114
|
+
def ap_first(fa: Applicative, fb: Applicative) -> Applicative:
|
|
115
|
+
"Sequence actions, disgarding the value of the second argument."
|
|
116
|
+
return fa.map2(lambda a, b: a, fb)
|
|
117
|
+
|
|
118
|
+
# (*>) : f a -> f b -> f b
|
|
119
|
+
def ap_second(fa: Applicative, fb: Applicative) -> Applicative:
|
|
120
|
+
"Sequence actions, disgarding the value of the first argument."
|
|
121
|
+
return ap(map(compose(identity, const), fa), fb)
|
|
122
|
+
|
|
123
|
+
# ATTN: implement when and unless here? Are they useful at all for us, as we don't need it for??
|
|
124
|
+
# when : Applicative f => Bool -> f () -> f ()
|
|
125
|
+
def when(f: type[Applicative], condition: bool, true_case: Applicative) -> Applicative:
|
|
126
|
+
return map(const(()), true_case) if condition else f.pure(())
|
|
127
|
+
|
|
128
|
+
# unless : Applicative f => Bool -> f () -> f ()
|
|
129
|
+
def unless(f: type[Applicative], condition: bool, false_case: Applicative) -> Applicative:
|
|
130
|
+
return f.pure(()) if condition else map(const(()), false_case)
|
|
131
|
+
|
|
132
|
+
# A copy of the Identity Functor that is only an Applicative
|
|
133
|
+
# This is useful as a default applicative in infrastructure
|
|
134
|
+
# modules that would lead to circularity if loading Identity
|
|
135
|
+
# module. See also IdentityM in case a default Monad is needed.
|
|
136
|
+
|
|
137
|
+
class IdentityA[A](Applicative):
|
|
138
|
+
"""A default Applicative that mimics Identity without Monad or Traversable.
|
|
139
|
+
|
|
140
|
+
This is useful in defaults only infrastructure modules in this package,
|
|
141
|
+
like Monad and Traversable, that Identity actually loads. Users should
|
|
142
|
+
not use this explicitly.
|
|
143
|
+
|
|
144
|
+
"""
|
|
145
|
+
__match_args__ = ('_value',)
|
|
146
|
+
|
|
147
|
+
def __init__(self, x: A):
|
|
148
|
+
self._value = x
|
|
149
|
+
|
|
150
|
+
def __str__(self):
|
|
151
|
+
return f'IdentityA {self._value}'
|
|
152
|
+
|
|
153
|
+
def __repr__(self):
|
|
154
|
+
return f'IdentityA({self._value})'
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def run(cls, fa: IdentityA[A]) -> A:
|
|
158
|
+
return fa._value
|
|
159
|
+
|
|
160
|
+
def map[B](self, g: Callable[[A], B]) -> IdentityA[B]:
|
|
161
|
+
return IdentityA(g(self._value))
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
def pure(cls, x: A) -> IdentityA[A]:
|
|
165
|
+
return IdentityA(x)
|
|
166
|
+
|
|
167
|
+
def map2[B, C](self, g: Callable[[A, B], C], fb: IdentityA[B]) -> IdentityA[C]:
|
|
168
|
+
return IdentityA(g(self._value, fb._value))
|
functorial/backwards.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
# Incomplete but working
|
|
3
|
+
|
|
4
|
+
from .applicative import Applicative
|
|
5
|
+
from .functions import flip
|
|
6
|
+
|
|
7
|
+
def Backwards(f: type[Applicative]):
|
|
8
|
+
class Backward_f(f):
|
|
9
|
+
"A Backward version of the Applicative f."
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def pure(cls, a):
|
|
13
|
+
return cls(f.pure(a))
|
|
14
|
+
|
|
15
|
+
def map2(self, g, fb):
|
|
16
|
+
return Backward_f(f(fb).map2(flip(g), f(self)))
|
|
17
|
+
|
|
18
|
+
return Backward_f
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#
|
|
2
|
+
# trait Bicofunctor (p : Type -> Type -> Type) where
|
|
3
|
+
# bicomap : (b -> a) -> (d -> c) -> p a c -> p b d
|
|
4
|
+
# bicomap f g = cofirst f . cosecond g
|
|
5
|
+
#
|
|
6
|
+
# cofirst : (b -> a) -> p a c -> p b c
|
|
7
|
+
# cofirst f = bicomap f identity
|
|
8
|
+
#
|
|
9
|
+
# cosecond : (d -> c) -> p a c -> p a d
|
|
10
|
+
# cosecond g : bicomap identity g
|
|
11
|
+
#
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections.abc import Callable
|
|
16
|
+
from typing import Protocol
|
|
17
|
+
|
|
18
|
+
from .functions import identity
|
|
19
|
+
|
|
20
|
+
__all__ = ['Bicofunctor', 'bicomap', 'cofirst', 'cosecond']
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Bicofunctor[A, C](Protocol):
|
|
24
|
+
# Subclasses MUST override at least ONE of these methods
|
|
25
|
+
def bicomap[B, D](self, f: Callable[[B], A], g: Callable[[C], D]) -> Bicofunctor[B, D]:
|
|
26
|
+
x = self.cosecond(g)
|
|
27
|
+
return x.cofirst(f)
|
|
28
|
+
|
|
29
|
+
def cofirst[B](self, f: Callable[[B], A]) -> Bicofunctor[B, C]:
|
|
30
|
+
return self.bicomap(f, identity)
|
|
31
|
+
|
|
32
|
+
def cosecond[D](self, g: Callable[[C], D]) -> Bicofunctor[A, D]:
|
|
33
|
+
return self.bicomap(identity, g)
|
|
34
|
+
|
|
35
|
+
def bicomap[A, B, C, D](f: Callable[[B], A], g: Callable[[C], D], x: Bicofunctor[A, C]) -> Bicofunctor[B, D]:
|
|
36
|
+
return x.bicomap(f, g)
|
|
37
|
+
|
|
38
|
+
def bicomap_[A, B, C, D](f: Callable[[B], A], g: Callable[[C], D]) -> Callable[[Bicofunctor[A, C]], Bicofunctor[B, D]]:
|
|
39
|
+
def do_bicomap_(x: Bicofunctor[A, C]) -> Bicofunctor[B, D]:
|
|
40
|
+
return x.bicomap(f, g)
|
|
41
|
+
|
|
42
|
+
return do_bicomap_
|
|
43
|
+
|
|
44
|
+
def cofirst[A, B, C](f: Callable[[B], A], x: Bicofunctor[A, C]) -> Bicofunctor[B, C]:
|
|
45
|
+
return x.cofirst(f)
|
|
46
|
+
|
|
47
|
+
def cosecond[A, C, D](g: Callable[[C], D], x: Bicofunctor[A, C]) -> Bicofunctor[A, D]:
|
|
48
|
+
return x.cosecond(g)
|
functorial/bifunctor.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
|
|
6
|
+
from .functor import Functor
|
|
7
|
+
from .functions import identity
|
|
8
|
+
|
|
9
|
+
__all__ = ['Bifunctor', 'bimap', 'bilift', 'map_first', 'map_second',]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# Functor as a mixin
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
class Bifunctor(Functor):
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def bimap[A, B, C, D](self, f: Callable[[A], C], g: Callable[[B], D]):
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
def map_first[A, C](self, f: Callable[[A], C]) -> Bifunctor:
|
|
22
|
+
return self.bimap(f, identity)
|
|
23
|
+
|
|
24
|
+
def map_second[B, D](self, g: Callable[[B], D]) -> Bifunctor:
|
|
25
|
+
return self.bimap(identity, g)
|
|
26
|
+
|
|
27
|
+
def map[B, D](self, g: Callable[[B], D]):
|
|
28
|
+
return self.map_second(g)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def bimap[A, B, C, D](f: Callable[[A], C], g: Callable[[B], D], bf: Bifunctor) -> Bifunctor:
|
|
32
|
+
"Maps functions over both components of a bifunctor, returning a transformed bifunctor."
|
|
33
|
+
return bf.bimap(f, g)
|
|
34
|
+
|
|
35
|
+
def bilift[A, B, C, D](f: Callable[[A], C], g: Callable[[B], D]):
|
|
36
|
+
"""Lifts functions on components to a mapping on bifunctors.
|
|
37
|
+
|
|
38
|
+
This is just the partial application bimap(f, g, __).
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
def lift_fg(bf: Bifunctor):
|
|
42
|
+
return bf.bimap(f, g)
|
|
43
|
+
|
|
44
|
+
return lift_fg
|
|
45
|
+
|
|
46
|
+
def map_first[A, C](f: Callable[[A], C], bf: Bifunctor) -> Bifunctor:
|
|
47
|
+
"Maps a function over the first component of a bifunctor."
|
|
48
|
+
return bf.bimap(f, identity)
|
|
49
|
+
|
|
50
|
+
def map_second[B, D](g: Callable[[B], D], bf: Bifunctor) -> Bifunctor:
|
|
51
|
+
"Maps a function over the second component of a bifunctor."
|
|
52
|
+
return bf.bimap(identity, g)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Bifunctors that can be traversed in order on both variables
|
|
3
|
+
#
|
|
4
|
+
# trait Bitraversable (f : Type -> Type -> Type) where
|
|
5
|
+
# traverse : (a -> f c) -> (b -> f d) -> t a b -> f (t c d)
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from abc import abstractmethod
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
|
|
13
|
+
from .applicative import Applicative, IdentityA
|
|
14
|
+
from .bifunctor import Bifunctor
|
|
15
|
+
from .functions import identity
|
|
16
|
+
from .wrappers import get_effect
|
|
17
|
+
|
|
18
|
+
__all__ = ['Bitraversable', 'bitraverse', 'bisequence']
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Bitraversable(Bifunctor):
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def bitraverse(self, f: type[Applicative], g1: Callable, g2: Callable) -> Applicative: # Hard to type properly in Python
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
def bitraverse( g1: Callable, g2: Callable, bt: Bitraversable, effect: type[Applicative] = IdentityA) -> Applicative:
|
|
27
|
+
"""Evaluates effectful functions at each element of a structure, giving the same shape in an effectful context.
|
|
28
|
+
|
|
29
|
+
Type: (Bitraversable t, Applicative f) => (a -> f c) -> (b -> f d) -> t a b -> f (t c d)
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
return bt.traverse(get_effect(g1) or get_effect(g2) or effect, g1, g2)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def bisequence(bt: Bitraversable, effect: type[Applicative] = IdentityA) -> Applicative:
|
|
36
|
+
"""Evaluate effects on each element of a structure, collecting the results in the effectful context.
|
|
37
|
+
|
|
38
|
+
Type: (Bitraversable t, Applicative f) => t a b -> f (t a b)
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
return bitraverse(identity, identity, bt, effect)
|
functorial/cofunctor.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Contravariant functors, the dual of Functor, or equivalently a functor on the op category.
|
|
3
|
+
#
|
|
4
|
+
# trait CoFunctor (f : Type -> Type) where
|
|
5
|
+
# comap : (b -> a) -> f a -> f b
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from abc import abstractmethod
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from typing import Protocol
|
|
13
|
+
|
|
14
|
+
from .functions import Function
|
|
15
|
+
|
|
16
|
+
__all__ = ['CoFunctor', 'comap', 'colift', 'Predicate']
|
|
17
|
+
|
|
18
|
+
#
|
|
19
|
+
# CoFunctor as a mixin
|
|
20
|
+
#
|
|
21
|
+
|
|
22
|
+
class CoFunctor(Protocol):
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def comap[A, B](self, g: Callable[[B], A]):
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
def comap[A, B](f: Callable[[B], A], cf: CoFunctor):
|
|
28
|
+
"""Maps a function over a contravariant functor, returning a transformed cofunctor.
|
|
29
|
+
|
|
30
|
+
"""
|
|
31
|
+
return cf.comap(f)
|
|
32
|
+
|
|
33
|
+
def colift[A, B](f: Callable[[B], A]):
|
|
34
|
+
"""Lifts a function to a mapping on cofunctors.
|
|
35
|
+
|
|
36
|
+
This is just the partial application comap(f, __).
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
def colift_f(cf: CoFunctor):
|
|
40
|
+
return cf.comap(f)
|
|
41
|
+
|
|
42
|
+
return colift_f
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
# Examples
|
|
47
|
+
#
|
|
48
|
+
|
|
49
|
+
class Predicate[A](Function, CoFunctor):
|
|
50
|
+
def __init__(self, predicate: Callable[[A], bool]):
|
|
51
|
+
super().__init__(predicate)
|
|
52
|
+
|
|
53
|
+
def __call__(self, x: A, *args, **kwds) -> bool:
|
|
54
|
+
return super().__call__(x, *args, **kwds)
|
|
55
|
+
|
|
56
|
+
def comap[B](self, g: Callable[[B], A]):
|
|
57
|
+
return self @ g
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# even = Predicate(lambda x: x % 2 == 0)
|
|
61
|
+
# odd = comap(inc, even)
|
|
62
|
+
#
|
|
63
|
+
# even(10)
|
|
64
|
+
# #=> True
|
|
65
|
+
# even(9)
|
|
66
|
+
# #=> False
|
|
67
|
+
# odd(10)
|
|
68
|
+
# #=> False
|
|
69
|
+
# even(9)
|
|
70
|
+
# #=> True
|