1use core::marker::PhantomData;
5
6use karpal_core::hkt::HKT;
7use karpal_core::monoid::Monoid;
8use karpal_core::semigroup::Semigroup;
9
10use crate::classes::{ApplicativeSt, ChainSt, FunctorSt};
11use crate::trans::MonadTrans;
12
13pub struct WriterTF<W, M>(PhantomData<(W, M)>);
20
21impl<W: 'static, M: HKT> HKT for WriterTF<W, M> {
22 type Of<A> = M::Of<(A, W)>;
23}
24
25impl<W: Monoid + 'static, M: FunctorSt> MonadTrans<M> for WriterTF<W, M> {
26 fn lift<A: 'static>(ma: M::Of<A>) -> M::Of<(A, W)> {
27 M::fmap_st(ma, |a| (a, W::empty()))
28 }
29}
30
31pub fn writer_t_pure<W: Monoid + 'static, M: ApplicativeSt, A: 'static>(a: A) -> M::Of<(A, W)> {
33 M::pure_st((a, W::empty()))
34}
35
36pub fn writer_t_fmap<W: 'static, M: FunctorSt, A: 'static, B: 'static>(
38 fa: M::Of<(A, W)>,
39 f: impl Fn(A) -> B + 'static,
40) -> M::Of<(B, W)> {
41 M::fmap_st(fa, move |(a, w)| (f(a), w))
42}
43
44pub fn writer_t_chain<W: Semigroup + Clone + 'static, M: ChainSt, A: 'static, B: 'static>(
48 fa: M::Of<(A, W)>,
49 f: impl Fn(A) -> M::Of<(B, W)> + 'static,
50) -> M::Of<(B, W)> {
51 M::chain_st(fa, move |(a, w1)| {
52 M::fmap_st(f(a), move |(b, w2)| {
53 let w1_owned = w1.clone();
54 (b, w1_owned.combine(w2))
55 })
56 })
57}
58
59pub fn writer_t_tell<W: 'static, M: ApplicativeSt>(w: W) -> M::Of<((), W)> {
61 M::pure_st(((), w))
62}
63
64pub fn writer_t_listen<W: Clone + 'static, M: FunctorSt, A: 'static>(
66 fa: M::Of<(A, W)>,
67) -> M::Of<((A, W), W)> {
68 M::fmap_st(fa, |(a, w): (A, W)| {
69 let w2 = w.clone();
70 ((a, w), w2)
71 })
72}
73
74#[allow(clippy::type_complexity)]
78pub fn writer_t_pass<W: 'static, M: FunctorSt, A: 'static>(
79 fa: M::Of<((A, Box<dyn Fn(W) -> W>), W)>,
80) -> M::Of<(A, W)> {
81 M::fmap_st(fa, |((a, f), w)| (a, f(w)))
82}
83
84pub fn writer_t_run<W, M: HKT, A>(fa: M::Of<(A, W)>) -> M::Of<(A, W)> {
86 fa
87}
88
89impl<W: 'static, M: FunctorSt> FunctorSt for WriterTF<W, M> {
92 fn fmap_st<A: 'static, B: 'static>(
93 fa: M::Of<(A, W)>,
94 f: impl Fn(A) -> B + 'static,
95 ) -> M::Of<(B, W)> {
96 writer_t_fmap::<W, M, A, B>(fa, f)
97 }
98}
99
100impl<W: Monoid + 'static, M: ApplicativeSt> ApplicativeSt for WriterTF<W, M> {
101 fn pure_st<A: 'static>(a: A) -> M::Of<(A, W)> {
102 writer_t_pure::<W, M, A>(a)
103 }
104}
105
106impl<W: Semigroup + Clone + 'static, M: ChainSt + FunctorSt> ChainSt for WriterTF<W, M> {
107 fn chain_st<A: 'static, B: 'static>(
108 fa: M::Of<(A, W)>,
109 f: impl Fn(A) -> M::Of<(B, W)> + 'static,
110 ) -> M::Of<(B, W)> {
111 writer_t_chain::<W, M, A, B>(fa, f)
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use karpal_core::hkt::{IdentityF, OptionF};
119
120 #[test]
121 fn writer_t_pure_identity() {
122 let result = writer_t_pure::<String, IdentityF, i32>(42);
123 assert_eq!(result, (42, String::new()));
124 }
125
126 #[test]
127 fn writer_t_pure_option() {
128 let result = writer_t_pure::<String, OptionF, i32>(42);
129 assert_eq!(result, Some((42, String::new())));
130 }
131
132 #[test]
133 fn writer_t_fmap_test() {
134 let val = writer_t_pure::<String, OptionF, i32>(10);
135 let result = writer_t_fmap::<String, OptionF, _, _>(val, |x| x * 3);
136 assert_eq!(result, Some((30, String::new())));
137 }
138
139 #[test]
140 fn writer_t_tell_test() {
141 let told = writer_t_tell::<String, OptionF>("hello".to_string());
142 assert_eq!(told, Some(((), "hello".to_string())));
143 }
144
145 #[test]
146 fn writer_t_chain_accumulates_log() {
147 let m1 = writer_t_tell::<String, OptionF>("a".to_string());
148 let result = writer_t_chain::<String, OptionF, _, _>(m1, |()| {
149 writer_t_tell::<String, OptionF>("b".to_string())
150 });
151 assert_eq!(result, Some(((), "ab".to_string())));
152 }
153
154 #[test]
155 fn writer_t_chain_with_value() {
156 let m1: Option<(i32, String)> = Some((10, "start".to_string()));
157 let result =
158 writer_t_chain::<String, OptionF, _, _>(m1, |x| Some((x + 5, " end".to_string())));
159 assert_eq!(result, Some((15, "start end".to_string())));
160 }
161
162 #[test]
163 fn writer_t_chain_none() {
164 let m1: Option<(i32, String)> = None;
165 let result =
166 writer_t_chain::<String, OptionF, _, _>(m1, |x| Some((x + 5, "end".to_string())));
167 assert_eq!(result, None);
168 }
169
170 #[test]
171 fn writer_t_listen_test() {
172 let val: Option<(i32, String)> = Some((42, "log".to_string()));
173 let result = writer_t_listen::<String, OptionF, i32>(val);
174 assert_eq!(result, Some(((42, "log".to_string()), "log".to_string())));
175 }
176
177 #[test]
178 fn writer_t_pass_test() {
179 let f: Box<dyn Fn(String) -> String> = Box::new(|w| w.to_uppercase());
180 let val: Option<((i32, Box<dyn Fn(String) -> String>), String)> =
181 Some(((42, f), "hello".to_string()));
182 let result = writer_t_pass::<String, OptionF, i32>(val);
183 assert_eq!(result, Some((42, "HELLO".to_string())));
184 }
185
186 #[test]
187 fn writer_t_lift_option() {
188 let lifted = WriterTF::<String, OptionF>::lift(Some(42));
189 assert_eq!(lifted, Some((42, String::new())));
190 }
191
192 #[test]
193 fn writer_t_lift_none() {
194 let lifted = WriterTF::<String, OptionF>::lift(None::<i32>);
195 assert_eq!(lifted, None);
196 }
197
198 #[test]
201 fn writer_t_functor_st_trait() {
202 let val = writer_t_pure::<String, OptionF, i32>(5);
203 let result = WriterTF::<String, OptionF>::fmap_st(val, |x| x + 1);
204 assert_eq!(result, Some((6, String::new())));
205 }
206
207 #[test]
208 fn writer_t_chain_st_trait() {
209 let val = WriterTF::<String, OptionF>::pure_st(5);
210 let result =
211 WriterTF::<String, OptionF>::chain_st(val, |x| Some((x + 10, "log".to_string())));
212 assert_eq!(result, Some((15, "log".to_string())));
213 }
214}
215
216#[cfg(test)]
217mod law_tests {
218 use super::*;
219 use karpal_core::hkt::OptionF;
220 use proptest::prelude::*;
221
222 proptest! {
223 #[test]
225 fn writer_t_functor_identity(a in any::<i16>(), w in "[a-z]{0,5}") {
226 let val: Option<(i16, String)> = Some((a, w.clone()));
227 let left = writer_t_fmap::<String, OptionF, _, _>(val.clone(), |x| x);
228 prop_assert_eq!(left, val);
229 }
230
231 #[test]
233 fn writer_t_monad_left_identity(a in -100i32..100) {
234 let f = |x: i32| -> Option<(i32, String)> {
235 Some((x + 1, "f".to_string()))
236 };
237 let left = writer_t_chain::<String, OptionF, _, _>(
238 writer_t_pure::<String, OptionF, _>(a),
239 f,
240 );
241 let right = f(a);
242 prop_assert_eq!(left, right);
243 }
244
245 #[test]
247 fn writer_t_monad_right_identity(a in any::<i16>(), w in "[a-z]{0,5}") {
248 let val: Option<(i16, String)> = Some((a, w));
249 let left = writer_t_chain::<String, OptionF, _, _>(
250 val.clone(),
251 |x| writer_t_pure::<String, OptionF, _>(x),
252 );
253 prop_assert_eq!(left, val);
254 }
255
256 #[test]
258 fn writer_t_lift_pure(a in any::<i32>()) {
259 let lift_pure = WriterTF::<String, OptionF>::lift(Some(a));
260 let pure_a = writer_t_pure::<String, OptionF, _>(a);
261 prop_assert_eq!(lift_pure, pure_a);
262 }
263 }
264}