frust/
hlist.rs

1use std::ops::Add;
2
3pub trait HList: Sized {
4    fn length(&self) -> u32;
5
6    fn push<H>(self, h: H) -> HCons<H, Self> {
7        HCons {
8            head: h,
9            tail: self,
10        }
11    }
12}
13
14/// Represents the right-most end of a heterogeneous list
15///
16/// Used to begin one:
17///
18/// ```
19/// # use frust::hlist::*;
20///
21/// let hlist1 = h_cons(1, HNil);
22/// let (h, _) = hlist1.pop();
23/// assert_eq!(h, 1);
24/// ```
25#[derive(PartialEq, Eq, Debug)]
26pub struct HNil;
27
28impl HList for HNil {
29    fn length(&self) -> u32 {
30        0
31    }
32}
33
34/// An HList is a heterogeneous list, one that is statically typed
35/// at compile time.
36///
37/// In simple terms, it is just a really deeply nested Tuple2.
38#[derive(PartialEq, Eq, Debug)]
39pub struct HCons<H, T> {
40    pub head: H,
41    pub tail: T,
42}
43
44impl<H, T: HList> HList for HCons<H, T> {
45    fn length(&self) -> u32 {
46        1 + self.tail.length()
47    }
48}
49
50impl<H, T> HCons<H, T> {
51    /// Returns the head of the list and the tail of the list as a tuple2.
52    /// The original list is consumed
53    ///
54    /// ```
55    /// # #[macro_use] extern crate frust; use frust::hlist::*; fn main() {
56    ///
57    /// let hlist1 = hlist!("hi");
58    /// let (h, _) = hlist1.pop();
59    /// assert_eq!(h, "hi");
60    /// # }
61    /// ```
62    pub fn pop(self) -> (H, T) {
63        (self.head, self.tail)
64    }
65}
66
67
68/// Takes an element and an Hlist and returns another one with
69/// the element prepended to the original list. The original list
70/// is consumed
71///
72/// ```
73/// # use frust::hlist::*;
74///
75/// let h_list = h_cons("what", h_cons(1.23f32, HNil));
76/// let (h1, tail) = h_list.pop();
77/// let (h2, _) = tail.pop();
78/// assert_eq!(h1, "what");
79/// assert_eq!(h2, 1.23f32);
80/// ```
81pub fn h_cons<H, T: HList>(h: H, tail: T) -> HCons<H, T> {
82    tail.push(h)
83}
84
85/// Create an HList
86///
87/// ```
88/// # #[macro_use] extern crate frust; use frust::hlist::*; fn main() {
89///
90/// let h = hlist![13.5f32, "hello", Some(41)];
91/// let (h1, tail1) = h.pop();
92/// let (h2, tail2) = tail1.pop();
93/// let (h3, tail3) = tail2.pop();
94/// assert_eq!(h1, 13.5f32);
95/// assert_eq!(h2, "hello");
96/// assert_eq!(h3, Some(41));
97/// assert_eq!(tail3, HNil);
98/// # }
99/// ```
100#[macro_export]
101macro_rules! hlist {
102
103    // Nothing
104    () => { HNil };
105
106    // Just a single item
107    ($single: expr) => {
108        HCons { head: $single, tail: HNil }
109    };
110
111    ($first: expr, $( $repeated: expr ), +) => {
112// Invoke recursive reversal of list that ends in the macro expansion implementation
113// of the reversed list
114        HCons { head: $first, tail: hlist!($($repeated), *)}
115    };
116
117}
118
119impl<RHS> Add<RHS> for HNil
120    where RHS: HList
121{
122    type Output = RHS;
123
124    fn add(self, rhs: RHS) -> RHS {
125        rhs
126    }
127}
128
129impl<H, T, RHS> Add<RHS> for HCons<H, T>
130    where T: Add<RHS>,
131          RHS: HList
132{
133    type Output = HCons<H, <T as Add<RHS>>::Output>;
134
135    fn add(self, rhs: RHS) -> Self::Output {
136        HCons {
137            head: self.head,
138            tail: self.tail + rhs,
139        }
140    }
141}
142
143pub trait IntoTuple2 {
144    type HeadType;
145    type TailOutput;
146
147    /// Turns an HList into nested Tuple2s, which are less troublesome to pattern match
148    /// and have a nicer type signature.
149    ///
150    /// ```
151    /// # #[macro_use] extern crate frust; use frust::hlist::*; fn main() {
152    /// let h = hlist![1, "hello", true, 42f32];
153    ///
154    /// // We now have a much nicer pattern matching experience
155    /// let (first,(second,(third, (fourth, _)))) = h.into_tuple2();
156    ///
157    /// assert_eq!(first ,       1);
158    /// assert_eq!(second, "hello");
159    /// assert_eq!(third ,     true);
160    /// assert_eq!(fourth,    42f32);
161    /// # }
162    /// ```
163    fn into_tuple2(self) -> (Self::HeadType, Self::TailOutput);
164}
165
166impl<T> IntoTuple2 for HCons<T, HNil> {
167    type HeadType = T;
168    type TailOutput = HNil;
169
170    fn into_tuple2(self) -> (Self::HeadType, Self::TailOutput) {
171        (self.head, HNil)
172    }
173}
174
175impl<T, Tail> IntoTuple2 for HCons<T, Tail>
176    where Tail: IntoTuple2
177{
178    type HeadType = T;
179    type TailOutput = (<Tail as IntoTuple2>::HeadType, <Tail as IntoTuple2>::TailOutput);
180
181    fn into_tuple2(self) -> (Self::HeadType, Self::TailOutput) {
182        (self.head, self.tail.into_tuple2())
183    }
184}
185
186#[cfg(test)]
187mod tests {
188
189    use super::*;
190
191    #[test]
192    fn test_hcons() {
193        let hlist1 = h_cons(1, HNil);
194        let (h, _) = hlist1.pop();
195        assert_eq!(h, 1);
196
197        let hlist2 = h_cons("hello", h_cons(1, HNil));
198        let (h2, tail2) = hlist2.pop();
199        let (h1, _) = tail2.pop();
200        assert_eq!(h2, "hello");
201        assert_eq!(h1, 1);
202    }
203
204    struct HasHList<T: HList>(T);
205
206    #[test]
207    fn test_contained_list() {
208        let c = HasHList(h_cons(1, HNil));
209        let retrieved = c.0;
210        assert_eq!(retrieved.length(), 1);
211        let new_list = h_cons(2, retrieved);
212        assert_eq!(new_list.length(), 2);
213    }
214
215    #[test]
216    fn test_macro() {
217        assert_eq!(hlist![], HNil);
218        let h = hlist![1, "2", 3];
219        let (h1, tail1) = h.pop();
220        assert_eq!(h1, 1);
221        assert_eq!(tail1, hlist!["2", 3]);
222        let (h2, tail2) = tail1.pop();
223        assert_eq!(h2, "2");
224        assert_eq!(tail2, hlist![3]);
225        let (h3, tail3) = tail2.pop();
226        assert_eq!(h3, 3);
227        assert_eq!(tail3, HNil);
228    }
229
230    #[test]
231    fn test_add() {
232        let h1 = hlist![true, "hi"];
233        let h2 = hlist![1, 32f32];
234        let combined = h1 + h2;
235        assert_eq!(combined, hlist![true, "hi", 1, 32f32])
236    }
237
238}