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.
Files changed (75) hide show
  1. functorial/__about__.py +4 -0
  2. functorial/__init__.py +29 -0
  3. functorial/all.py +54 -0
  4. functorial/alternative.py +36 -0
  5. functorial/applicative.py +168 -0
  6. functorial/backwards.py +18 -0
  7. functorial/bicofunctor.py +48 -0
  8. functorial/bifunctor.py +52 -0
  9. functorial/bitraversable.py +41 -0
  10. functorial/cofunctor.py +70 -0
  11. functorial/const.py +199 -0
  12. functorial/dev/__init__.py +1 -0
  13. functorial/dev/phases.py +93 -0
  14. functorial/dict.py +76 -0
  15. functorial/either.py +165 -0
  16. functorial/examples/__init__.py +1 -0
  17. functorial/examples/divide_conquer.py +71 -0
  18. functorial/examples/huffman.py +208 -0
  19. functorial/foldable.py +227 -0
  20. functorial/functions.py +419 -0
  21. functorial/functor.py +89 -0
  22. functorial/identity.py +55 -0
  23. functorial/io.py +161 -0
  24. functorial/lazy.py +47 -0
  25. functorial/list.py +382 -0
  26. functorial/maybe.py +204 -0
  27. functorial/monad.py +126 -0
  28. functorial/monoids.py +524 -0
  29. functorial/ntuple.py +131 -0
  30. functorial/ops.py +44 -0
  31. functorial/optics/__init__.py +23 -0
  32. functorial/optics/affine_fold.py +106 -0
  33. functorial/optics/affine_traversal.py +122 -0
  34. functorial/optics/all.py +26 -0
  35. functorial/optics/choice.py +93 -0
  36. functorial/optics/cochoice.py +54 -0
  37. functorial/optics/costrong.py +46 -0
  38. functorial/optics/deprecated/cartesian.py +65 -0
  39. functorial/optics/deprecated/forget.py +136 -0
  40. functorial/optics/deprecated/star.py +76 -0
  41. functorial/optics/fold.py +120 -0
  42. functorial/optics/generics.py +96 -0
  43. functorial/optics/getter.py +48 -0
  44. functorial/optics/iso.py +159 -0
  45. functorial/optics/ix_fold.py +133 -0
  46. functorial/optics/ix_lens.py +202 -0
  47. functorial/optics/ix_traversal.py +128 -0
  48. functorial/optics/lens.py +178 -0
  49. functorial/optics/make_lens.py +139 -0
  50. functorial/optics/optic.py +413 -0
  51. functorial/optics/prism.py +113 -0
  52. functorial/optics/profunctors.py +678 -0
  53. functorial/optics/re_.py +129 -0
  54. functorial/optics/review.py +156 -0
  55. functorial/optics/setter.py +64 -0
  56. functorial/optics/strong.py +75 -0
  57. functorial/optics/traversal.py +93 -0
  58. functorial/optics/vl_optics.py +462 -0
  59. functorial/pair.py +101 -0
  60. functorial/profunctor.py +61 -0
  61. functorial/py.typed +0 -0
  62. functorial/reader.py +94 -0
  63. functorial/set.py +45 -0
  64. functorial/singleton.py +214 -0
  65. functorial/state.py +110 -0
  66. functorial/traversable.py +112 -0
  67. functorial/trees.py +820 -0
  68. functorial/unit.py +10 -0
  69. functorial/utils.py +137 -0
  70. functorial/wrappers.py +52 -0
  71. functorial/writer.py +127 -0
  72. functorial-0.1.1.dist-info/METADATA +68 -0
  73. functorial-0.1.1.dist-info/RECORD +75 -0
  74. functorial-0.1.1.dist-info/WHEEL +4 -0
  75. functorial-0.1.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,4 @@
1
+ # SPDX-FileCopyrightText: 2024-present Christopher R. Genovese <genovese@cmu.edu>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ __version__ = "0.1.1"
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))
@@ -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)
@@ -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)
@@ -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