lambek/
type_app.rs

1//! Traits for the kind of unary type application, `Type -> Type`.
2//!
3//! Higher kinded types (HKT) such as `Type -> Type` are not natively
4//! supported in Rust. As such, we cannot use type constructors
5//! such as `Vec` without applying a specific type as an argument,
6//! e.g. `Vec<u8>`. Although the upcoming generic associated types (GAT)
7//! feature will partially solve this issue, the feature is not yet
8//! stable and may subject to changes.
9//! An alternative approach is to use _defunctionalization_ to encode
10//! regular Rust types to have kinds other than `Type`. [TypeApp]
11//! is one such trait for encoding types of kind `Type -> Type`.
12//!
13//! To promote a type constructor such as [Vec] to HKT, we define a
14//! proxy type [VecF] and implement [TypeCon] and [TypeApp] for them.
15//! We then use `VecF` as the unapplied version of `Vec` in our code.
16//! Inside type signatures, we use `App<VecF, X>` to apply `VecF`
17//! to a type `X`. `App<VecF, X>` is isomorphic to `Vec<X>`, and
18//! we can convert a value `xs: App<VecF, X>` back into `Vec<X>`
19//! by calling `xs.get_applied()`.
20//!
21//! The type [App] encapsulates the [TypeApp] constraint using
22//! [HasTypeApp]. With that, type signatures that use `App<F, X>`
23//! do not need to have `TypeApp<F, X>` in their trait bounds.
24//! This makes it significantly easier to perform arbitrary type
25//! applications without having to worry about polluting the
26//! trait bounds with `TypeApp` constraints. See
27//! [Functor](crate::functor::Functor) for a practical use of [App].
28
29use std::marker::PhantomData;
30
31/// A proxy type `F: TypeCon` to mark itself as having the kind
32/// `Type -> Type`.
33///
34/// The type `F` itself is never used directly, so it don't need to have
35/// any inhabitant and may be declared as an empty enum.
36/// Although the requirement is non-binding, types
37/// that implement `TypeCon` are also expected to implement [TypeApp].
38/// For stronger guarantee that a type `F` really implements
39/// [TypeApp] for all type arguments, use [TypeAppGeneric] instead.
40///
41/// In practice, it is usually sufficient to require type constructors
42/// to implement just [TypeCon]. This is because the constraint for
43/// [TypeAppGeneric] may sometimes be too strict, i.e. we may
44/// want to allow types that implement [TypeApp] for some
45/// constrained type arguments such as `Send` or `'static`.
46pub trait TypeCon {}
47
48/// A type `F: TypeApp<X>` have the associated type `Applied` as the
49/// result of applying a type `F` of kind `Type -> Type` to `X`.
50///
51/// More specifically, `TypeApp` is also parameterized by a lifetime
52/// `'a` to support application of types with limited lifetime.
53/// Unlike other functional languages, the higher kinded type
54/// application `F X` have to consider the case that both `F` and `X`
55/// may have different lifetimes. To deal with that, we require that
56/// a type `X` can only be applied to a type `F` if they both share
57/// a common lifetime `'a`. The result of the type application must
58/// also have the lifetime `'a`.
59///
60/// In practice, we typically define `F` to have `'static` lifetime,
61/// i.e. they do not contain references. On the other hand the type
62/// argument `X` _may_ contain references. For example, the result
63/// of `VecF: TypeApp<'a, &'aX>` would be `Vec<&'a X>`. A typical
64/// implementation of `TypeApp` would something like follows:
65///
66/// ```
67/// # use lambek::type_app::*;
68/// enum FooF {}
69/// struct Foo<X>(X);
70/// impl TypeCon for FooF {}
71/// impl<'a, X: 'a> TypeApp<'a, X> for FooF
72/// {
73///   type Applied = Foo<X>;
74/// }
75/// ```
76///
77/// A type constructor `F` may also choose to implement `TypeApp`
78/// for _unsized_ type arguments X, e.g. `dyn` trait objects.
79/// For example, we could define a type `BarF` to make the result
80/// of applying a type `X` into `dyn Bar<X>`:
81///
82/// ```
83/// # use lambek::type_app::*;
84/// enum BarF {}
85/// trait Bar<X>
86/// {
87///   fn bar(
88///     &self,
89///     x: X,
90///   );
91/// }
92/// impl TypeCon for BarF {}
93/// impl<'a, X: 'a> TypeApp<'a, X> for BarF
94/// {
95///   type Applied = dyn Bar<X> + 'a;
96/// }
97/// ```
98pub trait TypeApp<'a, X: 'a + ?Sized>: TypeCon + 'a
99{
100  type Applied: 'a + ?Sized;
101}
102
103pub type Applied<'a, F, X> = <F as TypeApp<'a, X>>::Applied;
104
105pub trait TypeAppGeneric: TypeCon + Sized
106{
107  fn with_type_app<'a, X: 'a, R: 'a, Cont: 'a>(cont: Box<Cont>) -> R
108  where
109    Self: 'a,
110    Cont: TypeAppCont<'a, Self, X, R>;
111}
112
113pub trait TypeAppGenericUnsized: TypeCon
114{
115  fn with_type_app<'a, X: 'a + ?Sized, R: 'a, Cont: 'a>(cont: Box<Cont>) -> R
116  where
117    Self: 'a,
118    Cont: TypeAppCont<'a, Self, X, R>;
119}
120
121pub trait TypeAppCont<'a, F: 'a + ?Sized, X: 'a + ?Sized, R: 'a>
122{
123  fn on_type_app(self: Box<Self>) -> R
124  where
125    F: TypeApp<'a, X>;
126}
127
128impl<F> TypeAppGeneric for F
129where
130  F: TypeAppGenericUnsized,
131{
132  fn with_type_app<'a, X: 'a, R: 'a, Cont: 'a>(cont: Box<Cont>) -> R
133  where
134    Self: 'a,
135    Cont: TypeAppCont<'a, Self, X, R>,
136  {
137    TypeAppGenericUnsized::with_type_app(cont)
138  }
139}
140
141/// Encapsulates an applied type into a trait object to prevent
142/// `TypeApp` constraints from propagating to type signatures.
143///
144/// A weakness of using [TypeApp] directly is that the trait bounds
145/// for every application is propagated to the type signatures
146/// that use them. Consider the Haskell `fmap` function of type
147/// `forall a b . f a -> (a -> b) -> f b`. If we naively use
148/// `TypeApp` to encode `fmap` in Rust, it would become something
149/// like:
150///
151/// ```
152/// # use lambek::type_app::*;
153/// trait Functor
154/// {
155///   fn fmap<'a, A, B>(
156///     fa: <Self as TypeApp<'a, A>>::Applied,
157///     map: impl Fn(A) -> B,
158///   ) -> <Self as TypeApp<'a, B>>::Applied
159///   where
160///     Self: TypeApp<'a, A>,
161///     Self: TypeApp<'a, B>;
162/// }
163/// ```
164///
165/// To use the above version of `fmap`, we would have to satisfy
166/// the two constraints `TypeApp<'a, A>` and `TypeApp<'a, B>`,
167/// even if we know a type `F` implements `TypeApp` for all
168/// types. This constraint can easily get out of hand especially
169/// if we use [TypeApp] within some higher abstractions such as
170/// [RowApp](crate::row::RowApp).
171///
172/// Notice that in most cases, functions like `fmap` treat the
173/// applied types as opaque, so they don't really need to know
174/// the concrete `Applied` type. We can therefore encapsulates
175/// the applied type into a trait object, and then convert it
176/// back to the concrete type only when we need it.
177///
178/// The `HasTypeApp` trait is implemented for all `Applied`
179/// associated type arise from any `F: TypeApp<'a, X>`.
180/// We wrap an `Applied` type into a
181/// `Box<dyn HasTypeApp<'a, F, X>>` to discharge the
182/// `TypeApp` constraint. When we need the concrete type
183/// again, we then call [get_applied_box](HasTypeApp::get_applied_box)
184/// which again requires the `TypeApp` trait bound to be present.
185///
186/// Using `HasTypeApp`, the trait `Functor` can instead be
187/// defined as:
188///
189/// ```
190/// # use lambek::type_app::*;
191/// trait Functor
192/// {
193///   fn fmap<'a, A, B>(
194///     fa: Box<dyn HasTypeApp<'a, Self, A>>,
195///     f: impl Fn(A) -> B,
196///   ) -> Box<dyn HasTypeApp<'a, Self, A>>;
197/// }
198/// ```
199///
200/// Notice that the `TypeApp` constraint is now gone, and code
201/// that use `fmap` no longer need to know whether a type `F`
202/// really implements `TypeApp` for all `X`. We can also use
203/// the type alias [App] so that we can write `App<'a, F, X>`
204/// instead of `Box<dyn HasTypeApp<'a, F, X>>`.
205///
206/// A downside of using `HasTypeApp` is that applied types have
207/// to be wrapped as a boxed trait object, which involves heap
208/// allocation. However the overhead can be minimal if the
209/// boxed values are reference types such as `Box<&FX>`.
210/// Take this consideration into account when you define a
211/// type constructor.
212pub trait HasTypeApp<'a, F: 'a + ?Sized, X: 'a + ?Sized>: 'a
213{
214  /// Get an applied type `FX` out of a
215  /// `Box<dyn HasTypeApp<'a, F, X>>` with the trait bound
216  /// `F: TypeApp<'a, X>` present.
217  fn get_applied_box(self: Box<Self>) -> Box<F::Applied>
218  where
219    F: TypeApp<'a, X>;
220
221  /// Get an reference to the applied type, `&FX`, out of a
222  /// `&dyn HasTypeApp<'a, F, X>` with the trait bound
223  /// `F: TypeApp<'a, X>` present.
224  fn get_applied_borrow(&self) -> &F::Applied
225  where
226    F: TypeApp<'a, X>;
227
228  /// Get a mutable reference to the applied type, `&mut FX`,
229  /// out of a `&mut dyn HasTypeApp<'a, F, X>` with the trait bound
230  /// `F: TypeApp<'a, X>` present.
231  fn get_applied_borrow_mut(&mut self) -> &mut F::Applied
232  where
233    F: TypeApp<'a, X>;
234
235  /// If we have a Rust value of type `&dyn HasTypeApp<'a, F, X>`,
236  /// we want to know for sure that `F` implements `TypeApp<'a, X>`.
237  /// We can use CPS to ask for a witness of such implementation
238  /// by calling `with_type_app` with a continuation implementing
239  /// [TypeAppCont]. The continuation is then called with the
240  /// trait bound `F: TypeApp<'a, X>` present.
241  fn with_type_app<'b>(
242    &self,
243    cont: Box<dyn TypeAppCont<'a, F, X, ()> + 'b>,
244  ) where
245    'a: 'b;
246}
247
248/// Newtype for a boxed value of [HasTypeApp].
249pub struct App<'a, F: 'a + ?Sized, X: 'a + ?Sized>(
250  pub Box<dyn HasTypeApp<'a, F, X>>,
251);
252
253pub trait CloneApp: TypeCon
254{
255  fn clone_app<'a, X: 'a>(fx: &App<'a, Self, X>) -> App<'a, Self, X>;
256}
257
258pub fn with_type_app<'a, 'b, F: 'a + ?Sized, X: 'a + ?Sized, R: 'a>(
259  applied: &'b App<'a, F, X>,
260  cont: impl TypeAppCont<'a, F, X, R>,
261) -> R
262where
263  'a: 'b,
264{
265  struct WrapCont<'b, Cont, R>
266  {
267    cont: Box<Cont>,
268    result: &'b mut Option<R>,
269    phantom: PhantomData<R>,
270  }
271
272  impl<'a, 'b, F: 'a + ?Sized, X: 'a + ?Sized, R: 'a, Cont>
273    TypeAppCont<'a, F, X, ()> for WrapCont<'b, Cont, R>
274  where
275    Cont: TypeAppCont<'a, F, X, R>,
276  {
277    fn on_type_app(self: Box<Self>)
278    where
279      F: TypeApp<'a, X>,
280    {
281      let res = self.cont.on_type_app();
282      *self.result = Some(res);
283    }
284  }
285
286  let mut result: Option<R> = None;
287  let wrap_cont = WrapCont {
288    cont: Box::new(cont),
289    result: &mut result,
290    phantom: PhantomData,
291  };
292
293  applied.with_type_app(Box::new(wrap_cont));
294
295  result.unwrap()
296}
297
298impl<'a, F: 'a + ?Sized, X: 'a + ?Sized> App<'a, F, X>
299{
300  pub fn get_applied(self) -> F::Applied
301  where
302    F: TypeApp<'a, X>,
303    F::Applied: Sized,
304  {
305    *self.0.get_applied_box()
306  }
307}
308
309impl<'a, F: 'a + ?Sized, X: 'a + ?Sized> HasTypeApp<'a, F, X> for App<'a, F, X>
310{
311  fn get_applied_box(self: Box<Self>) -> Box<F::Applied>
312  where
313    F: TypeApp<'a, X>,
314  {
315    self.0.get_applied_box()
316  }
317
318  /// Get an reference to the applied type, `&FX`, out of a
319  /// `&dyn HasTypeApp<'a, F, X>` with the trait bound
320  /// `F: TypeApp<'a, X>` present.
321  fn get_applied_borrow(&self) -> &F::Applied
322  where
323    F: TypeApp<'a, X>,
324  {
325    self.0.get_applied_borrow()
326  }
327
328  fn get_applied_borrow_mut(&mut self) -> &mut F::Applied
329  where
330    F: TypeApp<'a, X>,
331  {
332    self.0.get_applied_borrow_mut()
333  }
334
335  fn with_type_app<'b>(
336    &self,
337    cont: Box<dyn TypeAppCont<'a, F, X, ()> + 'b>,
338  ) where
339    'a: 'b,
340  {
341    self.0.with_type_app(cont)
342  }
343}
344
345/// Wraps a type `FX` into [App] in the presence of the [TypeApp]
346/// constraint, allowing subsequent use of [App] to not depend
347/// on [TypeApp].
348pub fn wrap_app<'a, F: 'a, X: 'a, FX: 'a>(fx: FX) -> App<'a, F, X>
349where
350  F: TypeApp<'a, X, Applied = FX>,
351{
352  struct Applied<X>(X);
353
354  impl<'a, F: 'a, X: 'a, FX: 'a> HasTypeApp<'a, F, X> for Applied<FX>
355  where
356    F: TypeApp<'a, X, Applied = FX>,
357  {
358    fn get_applied_box(self: Box<Self>) -> Box<FX>
359    {
360      Box::new(self.0)
361    }
362
363    fn get_applied_borrow(&self) -> &FX
364    {
365      &self.0
366    }
367
368    fn get_applied_borrow_mut(&mut self) -> &mut FX
369    {
370      &mut self.0
371    }
372
373    fn with_type_app<'b>(
374      &self,
375      cont: Box<dyn TypeAppCont<'a, F, X, ()> + 'b>,
376    ) where
377      'a: 'b,
378    {
379      cont.on_type_app()
380    }
381  }
382
383  App(Box::new(Applied(fx)))
384}
385
386#[macro_export]
387macro_rules! define_type_app {
388  ( $proxy:ident, $target:ident ) => {
389    pub enum $proxy {}
390    $crate::impl_type_app!($proxy, $target);
391  };
392  ( $proxy:ident < $( $types:ident ),+ $(,)? >, $target:ident ) => {
393    #[allow(unused_parens)]
394    pub struct $proxy < $( $types ),* >
395      ( std::marker::PhantomData< ( $( $types ),* ) > );
396
397    $crate::impl_type_app!($proxy <$( $types ),* >, $target);
398  };
399}
400
401#[macro_export]
402macro_rules! impl_type_app {
403  ( $proxy:ident, $target:ident ) => {
404    impl TypeCon for $proxy {}
405
406    impl < 'a, X: 'a > TypeApp < 'a, X > for $proxy {
407      type Applied = $target < X >;
408    }
409
410    impl TypeAppGeneric for $proxy
411    {
412      fn with_type_app<'a, X : 'a, R : 'a, Cont: 'a>(
413        cont : Box < Cont >
414      ) -> R
415      where
416        Self : 'a,
417        Cont: TypeAppCont<'a, Self, X, R>,
418      {
419        cont.on_type_app()
420      }
421    }
422  };
423  ( $proxy:ident < $( $types:ident ),+ $(,)? >, $target:ident ) => {
424    impl < $( $types ),* >
425      TypeCon for $proxy < $( $types ),* > {}
426
427    impl < 'a, X: 'a, $( $types : 'a ),* >
428      TypeApp < 'a, X > for $proxy < $( $types ),* >
429    {
430      type Applied = $target < $( $types ),*, X >;
431    }
432
433    impl < $( $types ),* >
434      TypeAppGeneric for $proxy < $( $types ),* >
435    {
436      fn with_type_app<'a, X : 'a, R : 'a, Cont: 'a>(
437        cont : Box < Cont >
438      ) -> R
439      where
440        Self : 'a,
441        Cont: TypeAppCont<'a, Self, X, R>,
442      {
443        cont.on_type_app()
444      }
445    }
446  }
447}
448
449pub struct Compose<F: ?Sized, G: ?Sized>(PhantomData<F>, PhantomData<G>);
450
451impl<F: ?Sized, G: ?Sized> TypeCon for Compose<F, G> {}
452
453impl<'a, F: 'a + ?Sized, G: 'a + ?Sized, X: 'a + ?Sized, FX: 'a, GX: 'a>
454  TypeApp<'a, X> for Compose<F, G>
455where
456  G: TypeApp<'a, X, Applied = GX>,
457  F: TypeApp<'a, GX, Applied = FX>,
458{
459  type Applied = FX;
460}
461
462pub struct ComposeApp<F: ?Sized, G: ?Sized>(PhantomData<F>, PhantomData<G>);
463
464impl<F: ?Sized, G: ?Sized> TypeCon for ComposeApp<F, G> {}
465
466impl<'a, F: 'a + ?Sized, G: 'a + ?Sized, X: 'a + ?Sized> TypeApp<'a, X>
467  for ComposeApp<F, G>
468{
469  type Applied = App<'a, F, App<'a, G, X>>;
470}
471
472/// `App<Identity, X> ~ X`
473///
474/// A type `X` applied to `Identity` always give us back `X` itself.
475///
476/// Unlike in Haskell, the `Applied` result can just be an `X`,
477/// instead of getting wrapped around a newtype.
478pub enum Identity {}
479
480impl TypeCon for Identity {}
481
482impl<'a, X: 'a + ?Sized> TypeApp<'a, X> for Identity
483{
484  type Applied = X;
485}
486
487impl TypeAppGeneric for Identity
488{
489  fn with_type_app<'a, X: 'a, R: 'a, Cont: 'a>(cont: Box<Cont>) -> R
490  where
491    Self: 'a,
492    Cont: TypeAppCont<'a, Self, X, R>,
493  {
494    cont.on_type_app()
495  }
496}
497
498/// `App<Const<A>, X> ~ A`
499///
500/// A type `X` applied to `Const<A>` simply has the type argument
501/// discarded. So the type application result is always `A`.
502///
503/// Unlike in Haskell, the `Applied` result can just be an `A`,
504/// instead of getting wrapped around a newtype.
505pub struct Const<A: ?Sized>(PhantomData<A>);
506
507impl<A: ?Sized> TypeCon for Const<A> {}
508
509impl<'a, A: 'a + ?Sized, X: 'a + ?Sized> TypeApp<'a, X> for Const<A>
510{
511  type Applied = A;
512}
513
514pub struct AppF<F: ?Sized>(PhantomData<F>);
515
516impl<F: ?Sized> TypeCon for AppF<F> {}
517
518impl<'a, X: 'a + ?Sized, F: 'a + ?Sized> TypeApp<'a, X> for AppF<F>
519{
520  type Applied = App<'a, F, X>;
521}
522
523/// `App<VecF, X> ~ Vec<X>`
524pub enum VecF {}
525impl_type_app!(VecF, Vec);
526
527/// `App<OptionF, X> ~ Option<X>`
528pub enum OptionF {}
529impl_type_app!(OptionF, Option);
530
531/// `App<ResultF<E>, X> ~ Result<E, X>`
532pub struct ResultF<E>(PhantomData<E>);
533
534impl<E> TypeCon for ResultF<E> {}
535
536impl<'a, E: 'a, X: 'a> TypeApp<'a, X> for ResultF<E>
537{
538  type Applied = Result<X, E>;
539}