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}