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