├── Cargo.toml ├── LICENSE ├── readme.md └── src ├── applicative.rs ├── core.rs ├── functor.rs ├── impls.rs ├── lib.rs ├── meltdown.rs ├── monad.rs └── writeup.md /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "type-plugs" 3 | version = "0.1.0" 4 | authors = ["Edmund Smith "] 5 | readme = "readme.md" 6 | license = "MIT" 7 | keywords = ["monad", "hkt", "gat", "typelevel", "plug"] 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Edmund Smith 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Type Plugs - A Method for emulating Higher-Kinded Types in Rust 2 | 3 | This is a small demonstration of a technique for emulating Higher-Kinded Types (HKTs)/Generalised Associated Types(GATs) through existing language features. 4 | Basically, you can emulate `T::F` as being `>::result_t` with the `Plug` trait, and you can destructure `F` down to `F<_>, A` through the `Unplug` trait. 5 | 6 | 7 | You can read more about how this works [with the original post](https://gist.github.com/edmundsmith/855fcf0cb35dd467c29a9350481f0ecf) and [the followup](https://gist.github.com/edmundsmith/e09d5f473172066c0023ef84ee830cad). 8 | 9 | -------------------------------------------------------------------------------- /src/applicative.rs: -------------------------------------------------------------------------------- 1 | use core::*; 2 | use functor::*; 3 | 4 | pub trait Applicative: Functor { 5 | fn pure(s:Self::A) -> Self; 6 | fn app(f:plug!(Self[F]), s:Self) -> plug!(Self[B]) where 7 | F:Fn(Self::A) -> B, 8 | Self:Plug+Plug+Unplug, 9 | plug!(Self[F]):Unplug+Plug+Clone, 10 | Self::F:Plug 11 | ; 12 | } 13 | 14 | impl Applicative for Box { 15 | fn pure(a:A) -> Self { 16 | Box::new(a) 17 | } 18 | 19 | fn app(f:plug!(Self[F]), s:Self) -> plug!(Self[B]) 20 | where 21 | F:Fn(Self::A) -> B 22 | { 23 | Box::new((*f)(*s)) 24 | } 25 | } 26 | 27 | impl Applicative for Vec { 28 | fn pure(a:A) -> Self { 29 | vec![a] 30 | } 31 | fn app(fs:plug!(Self[F]), s:Self) -> plug!(Self[B]) 32 | where 33 | F:Fn(Self::A) -> B, 34 | plug!(Self[F]): Clone, 35 | { 36 | let flat:Vec = 37 | Functor::map(|x:A| 38 | Functor::map(|f:F| 39 | f(x.clone()), 40 | fs.clone()), 41 | s).into_iter().flatten().collect(); 42 | flat 43 | } 44 | } 45 | 46 | impl Applicative for Option { 47 | fn pure(a:A) -> Self { 48 | Some(a) 49 | } 50 | fn app(fs:plug!(Self[F]), s:Self) -> plug!(Self[B]) 51 | where 52 | F:Fn(Self::A) -> B 53 | { 54 | match fs { 55 | Some(f) => match s { 56 | Some(x) => Some(f(x)), 57 | None => None 58 | }, 59 | None => None 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/core.rs: -------------------------------------------------------------------------------- 1 | 2 | #[allow(non_camel_case_types, bare_trait_objects)] 3 | pub struct forall_t; 4 | 5 | pub struct Concrete,A> 6 | { 7 | pub unwrap:>::result_t 8 | } 9 | 10 | impl,A> Clone for Concrete where >::result_t:Clone, >::result_t:Unplug { 11 | fn clone(&self) -> Self { 12 | Concrete::of(self.unwrap.clone()) 13 | } 14 | } 15 | 16 | impl,A> Concrete { 17 | pub fn of+Plug>(x:MA) -> Self where M:Plug { 18 | Concrete { unwrap: x } 19 | } 20 | } 21 | 22 | pub trait Unplug:Sized { 23 | type F:Unplug+Plug; 24 | type A; 25 | } 26 | 27 | pub trait Plug:Sized { 28 | type result_t:Plug+Unplug; 29 | } 30 | 31 | impl+Plug+Unplug,A,B> Plug for Concrete { 32 | type result_t = Concrete; 33 | } 34 | 35 | impl+Unplug,A> Unplug for Concrete { 36 | type F = M; 37 | type A = A; 38 | } 39 | 40 | macro_rules! plug { 41 | ($t1:ty [ $t2:ty ]) => { 42 | <$t1 as Plug<$t2>>::result_t 43 | }; 44 | } 45 | 46 | macro_rules! unplug { 47 | ($t:ty, $v:ident) => { 48 | <$t as Unplug>::$v 49 | }; 50 | } -------------------------------------------------------------------------------- /src/functor.rs: -------------------------------------------------------------------------------- 1 | use core::*; 2 | 3 | pub trait Functor: Unplug+Plug { 4 | fn map(f:F, s:Self) -> plug!(Self[B]) where 5 | Self:Plug, 6 | F:Fn(Self::A) -> B 7 | ; 8 | } 9 | 10 | impl Functor for Box { 11 | fn map(f:F, s:Self) -> plug!(Self[B]) where 12 | F:Fn(Self::A) -> B 13 | { 14 | Box::new(f(*s)) 15 | } 16 | } 17 | 18 | impl Functor for Vec { 19 | fn map(f:F, s:Self) -> plug!(Self[B]) where 20 | F:Fn(Self::A) -> B 21 | { 22 | s.into_iter().map(f).collect() 23 | } 24 | } 25 | 26 | 27 | impl Functor for Option { 28 | fn map(f:F, s:Self) -> plug!(Self[B]) where 29 | F:Fn(Self::A) -> B 30 | { 31 | s.map(f) 32 | } 33 | } 34 | 35 | #[cfg(test)] 36 | fn it_compiles(functor:F, fun:impl Fn(A)->B, fun2:impl Fn(B)->C) -> >::result_t where 37 | F:Plug+Plug+Plug+Unplug, 38 | ::F: Plug + Plug + Plug 39 | { 40 | let cmp = |x|fun2(fun(x)); 41 | Functor::map(cmp, functor) 42 | } 43 | -------------------------------------------------------------------------------- /src/impls.rs: -------------------------------------------------------------------------------- 1 | use core::{Plug, Unplug, forall_t}; 2 | 3 | macro_rules! simple_impl { 4 | ($name:ident) => { 5 | impl Plug for $name{ 6 | type result_t = $name; 7 | } 8 | 9 | impl Unplug for $name { 10 | type F = $name; 11 | type A = A; 12 | } 13 | }; 14 | } 15 | 16 | simple_impl!(Box); 17 | simple_impl!(Vec); 18 | simple_impl!(Option); -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | pub mod core; 3 | pub mod functor; 4 | pub mod applicative; 5 | pub mod monad; 6 | pub mod impls; 7 | 8 | 9 | #[cfg(test)] 10 | mod tests { 11 | #[test] 12 | fn it_works() { 13 | //::core::main(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/meltdown.rs: -------------------------------------------------------------------------------- 1 | use core::*; 2 | 3 | #[test] 4 | fn main() { 5 | let fv = Concrete::of(vec![1,2,3i32]); 6 | //Concrete::of helps the compiler infer the types through constraint manipulation; 7 | //simply using the naked constructor might fail to resolve the types 8 | //let fv = Concrete{unwrap:vec![1,2,3i32]}; 9 | 10 | let fvr = Functor::map((|x|x as i64+1) as fn(i32)->i64,fv); 11 | let avr = Applicative::app(Concrete::of(vec![(|x:i64|x+1) as fn(i64)->i64,|x:i64|-x]), fvr); 12 | println!("{:?}", avr.unwrap); 13 | } -------------------------------------------------------------------------------- /src/monad.rs: -------------------------------------------------------------------------------- 1 | use core::*; 2 | use applicative::*; 3 | 4 | pub trait Monad : Applicative { 5 | fn bind(f:F, s:Self) -> plug!(Self[B]) 6 | where 7 | Self:Plug+Plug, 8 | F:Fn(Self::A) -> plug!(Self[B]) 9 | ; 10 | } 11 | 12 | impl Monad for Box { 13 | fn bind(f:F, s:Self) -> plug!(Self[B]) 14 | where 15 | Self:Plug+Plug, 16 | F:Fn(Self::A) -> plug!(Self[B]) 17 | { 18 | f(*s) 19 | } 20 | } 21 | 22 | impl Monad for Vec { 23 | fn bind(f:F, s:Self) -> plug!(Self[B]) 24 | where 25 | F:Fn(Self::A) -> plug!(Self[B]) 26 | { 27 | let res:Vec = 28 | s 29 | .into_iter() 30 | .flat_map(f).collect(); 31 | res 32 | } 33 | } 34 | 35 | impl Monad for Option { 36 | fn bind(f:F, s:Self) -> plug!(Self[B]) 37 | where 38 | F:Fn(Self::A) -> plug!(Self[B]) 39 | { 40 | match s { 41 | Some(x) => f(x), 42 | None => None 43 | } 44 | } 45 | } 46 | 47 | #[cfg(test)] 48 | mod tests { 49 | use super::*; 50 | ///wew lad 51 | fn higher_poly_demo<'a,M:Monad,A:'a+Clone,B:'a+Clone,F>(m:M, f:F) -> >::result_t where 52 | M:Plug+Plug+Unplug,//+Plug+Plug>::result_t>, 53 | M:Plug>::result_t>>, 54 | M:Plug, 55 | F:'static, 56 | ::F:Plug+Plug, 57 | >::result_t:Monad+Unplug+'a, 58 | <>::result_t as Unplug>::F:Plug, 59 | F:Fn(A) -> B+'a, 60 | //F:Fn(A) -> >::result_t + Clone, 61 | { 62 | let cl = Box::new(move |x|Applicative::pure(f(x))); 63 | Monad::bind::_>,B>(cl as Box_>, m) 64 | } 65 | 66 | #[test] 67 | fn use_higher_poly() { 68 | let f = |x|x+1; 69 | let p1 = Some(5); 70 | let p2 = vec![5]; 71 | let p3 = Box::new(5); 72 | assert!(higher_poly_demo(p1, f) == Some(6)); 73 | assert!(higher_poly_demo(p2, f) == vec![6]); 74 | assert!(higher_poly_demo(p3, f) == Box::new(6)); 75 | } 76 | } -------------------------------------------------------------------------------- /src/writeup.md: -------------------------------------------------------------------------------- 1 | # Method for Emulating Higher-Kinded Types in Rust 2 | 3 | ## Intro 4 | 5 | I've been fiddling about with an idea lately, looking at how higher-kinded types can be represented in such a way that we can reason with them in Rust here and now, without having to wait a couple years for what would be a significant change to the language and compiler. 6 | 7 | There have been multiple discussions on introducing higher-ranked polymorphism into Rust, using Haskell-style Higher-Kinded Types (HKTs) or Scala-looking Generalised Associated Types (GATs). The benefit of higher-ranked polymorphism is in the name; it would allow higher-level abstractions and pattern expression than just the rank-1 polymorphism we have today. 8 | 9 | As an example, currently we can express this type: 10 | ```rust 11 | fn example(x:Vec, f:Fn(A)->B) -> Vec; 12 | ``` 13 | but we can't express the more generic 14 | ```rust 15 | fn example2< 16 | forall A.M:Container, //imaginary syntax 17 | A, 18 | B 19 | >(x:M, f:Fn(A)->B) -> M; 20 | ``` 21 | as while we can express `forall A.Vec` there is no way to currently express the type```forall M.forall A.M``` (note: the nesting a `forall` within another `forall` type is what gives us our rank-2-ness of the type). 22 | 23 | Haskell solves this problem with Higher-Kinded Types, where not only can we express the type `List` parameterised with `Int` as `List Int :: *`, we can also express and reason about `List` on its own: `List :: * -> *`. Current Rust doesn't allow this, as all generic types must be fully instantiated (i.e. `Vec` exists, but `Vec` does not). 24 | 25 | The GAT approach suggests solving this by allowing generic types within traits, as in 26 | ```rust 27 | trait VecGAT { 28 | type Applied = Vec; 29 | } 30 | ``` 31 | so now we can express the unapplied `Vec` by our `VecGAT`, and `(Vec) i32` is expressed by `VecGAT::Applied`. This is a pretty Rust-y solution, and is easy enough to understand, but has the drawback of only really existing on paper, for the time being at least. There is an excellent article on [implementing Monads in Rust with GATs here](https://varkor.github.io/blog/2019/03/28/idiomatic-monads-in-rust.html) - if only we could wait for Rust to have GATs implemented and syntax bikeshedded and semantics satiated. 32 | 33 | But alas, this is still a proposal a ways off, and we have navel-gazing to do today. 34 | 35 | ## First attempts 36 | 37 | Rust already has a pretty solid constraint system in its traits and associated types if you know how to (ab)use them. As a teen I spent my evenings ~~contorting the Scala type system~~ revising, so I had a pop at expressing a representation for unapplied generic types: 38 | ```rust 39 | struct forall_t; //The 'unapplied' type 40 | //Expressing M as distinct M and A types 41 | struct Lift(PhantomData,PhantomData); 42 | trait Lower { 43 | fn (lifted:Lift< 44 | ::unapplied_t, 45 | ::param_t> 46 | ) -> Self; 47 | } 48 | ``` 49 | plus an isomorphism between`M` and `Lift,A>`. 50 | 51 | This proved hard to use, not least because the `Lift` type has zero size. I would employ the wonderfully unsafe `core::mem::transmute` between `Box>` and `Box,A>>`, resulting in segfaults once optimisations were turned on. It's also pretty difficult to properly relay the meaning of `Isomorphism` to the compiler when we are working with a new representation, as our assumptions about things like `to . fro = fro . to = id` have to be proven from scratch to make anything useful typecheck. 52 | 53 | However, this first failed approach was not entirely fruitless - I ended up writing a trait `Unapply` which would come in handy later. 54 | ```rust 55 | trait Unapply { 56 | type unapplied_t; 57 | type param_t; 58 | } 59 | impl Unapply for Vec { 60 | type unapplied_t = Vec; 61 | type param_t = A; 62 | } 63 | ``` 64 | For a given type `M:Unapply`, I could *almost* close the isomorphism loop through my lifting and unlifting traits: 65 | `M --[M as Unapply]--> (M, A) --[M as Lower]-->M`. This loop worked when I had a concrete `M` as my starting point to go *to* `(M,A)`, but the inference required inferring `M:Lower`, which I couldn't do *from* `(M,A)` 66 | 67 | ## Breakthrough #1 68 | 69 | So, currently Rust can't express a trait accessible via `MyTrait::GenericTy`. But what about moving the generic parameter left once, to get `MyTrait::GenericTy`? 70 | ```rust 71 | trait ReplaceWith { 72 | type result_t; 73 | } 74 | impl ReplaceWith for Vec { 75 | type result_t = Vec; 76 | } 77 | ``` 78 | Now we get ` as ReplaceWith>::result_t == Vec`. While the syntax is unwieldy, this works to emulate GATs/HKTs: what might ideally be represented as `Ty::GAT` in a future syntax can today be expressed as `>::result_t`. 79 | 80 | ## Breakthrough 2 81 | 82 | Now we have simple GATs expressible, let's get to working on our representations of higher-kinded types. 83 | 84 | Firstly, a given type must be liftable to be supported in our ad-hoc representation system. I'll call this `Unplug`, as we are conceptually operating on some `M` to separate the `M<_>` from the `A`. 85 | ```rust 86 | trait Unplug { 87 | type F; //The representation type of the higher-kinded type 88 | type A; //The parameter type 89 | } 90 | impl Unplug for Vec { 91 | type F=Vec; //All unapplied Vecs are represented by 92 | //Vec 93 | type A=A; 94 | } 95 | ``` 96 | And to re-combine a split application (to re-plug an unplugged type): 97 | ```rust 98 | trait Plug { 99 | type result_t; 100 | } 101 | //This is identical to the ReplaceWith trait shown above 102 | impl Plug for Vec { 103 | type result_t = Vec; 104 | } 105 | ``` 106 | so now we can take a `Vec`, split it into separate `Vec<_>` and `A` types, and re-apply a new type to `Vec<_>` to get a `Vec` if we want to. 107 | 108 | This is pretty close to complete for the basics of our representation of higher-kinded types. We can now plug and unplug parameters from HKTs like `Option<_>` and `Vec<_>`, so long as we provide the instances to tell the compiler how. 109 | 110 | Next up is our wrapper, so that we can hold values with some type `M` while still being able to reason about the `M<_>` and the `T` separately. Whereas before I tried some pointer/transmute/phantom reference shenanigans, I eventually found that I could just store the underlying value (woops), now that I had a way to express its type given the unplugged types available to the wrapper. 111 | ```rust 112 | pub struct Concrete,A> { 113 | pub unwrap:>::result_t 114 | } 115 | //Conceptually equivalent to the GAT-syntax 116 | //pub struct Concrete{unwrap: M::Plug} 117 | ``` 118 | (Concrete is a holdover name from when I tried juggling lifted pointers and phantoms, but fits as 'the real value represented by our constructed type `(M,A)`). 119 | This struct has some rather nice properties, once you help the type inference out a bit. To this end, I created a helper function to guide the inference: 120 | ```rust 121 | impl,A> Concrete { 122 | fn of+Plug>(x:MA) -> Self 123 | where M:Plug { 124 | Concrete { unwrap: x } 125 | } 126 | } 127 | ``` 128 | The associated types in the signature are used to close the loop of `plug . unplug` and `unplug . plug`, so the compiler recognises that we are working on the same `plug (M<_>,A) == MA` and `unplug MA == (M<_>,A)`. A lot of this feels like working in a weird Prolog-Idris hybrid at times. 129 | The type-inference hinting out of the way, let's take a quick look at use: 130 | ```rust 131 | let myVec = vec![1,2,3i32]; 132 | //Wrapping 133 | let conc = Concrete::of(myVec); 134 | //Unwrapping 135 | let myVec = conc.unwrap; 136 | ``` 137 | This should be a zero-cost abstraction! Woohoo! 138 | 139 | ## Putting it to work 140 | 141 | Now we can express higher-kinded types, what do we get? 142 | 143 | Well, for our demonstration, I will cook up everyone's favourite burrito\* - the Monad! 144 | (\*this is the last time a monad will be described as a burrito). 145 | 146 | In Haskell, the typeclass hierarchy for a Monad goes 147 | `Functor f => Applicative f => Monad f`. 148 | For familiarity's sake, I'll start with `Functor`: 149 | ```Haskell 150 | class Functor f where 151 | fmap :: (a -> b) -> f a -> f b 152 | ``` 153 | is written in our representation as 154 | ```rust 155 | pub trait Functor: Unplug+Plug<::A> { 156 | //Self is conceptually our haskell-ese "f a" 157 | fn map(f:F, s:Self) -> >::result_t 158 | where 159 | Self:Plug, 160 | F:FnMut(::A) -> B 161 | ; 162 | } 163 | 164 | //Example impl for a represented Vec 165 | impl Functor for Concrete,A> { 166 | //remember, Self ~ (Vec<_>, A) ~ "f a" 167 | fn map(f:F, s:Self) -> >::result_t 168 | where 169 | F:FnMut(::A) -> B 170 | { 171 | Concrete::of(s.unwrap.into_iter().map(f).collect()) 172 | } 173 | } 174 | ``` 175 | To show functor-level polymorphism in action, here's a simple compose-then-map function: 176 | ```rust 177 | fn functor_test( 178 | functor:F, 179 | fun:impl Fn(A)->B, 180 | fun2:impl Fn(B)->C 181 | ) -> >::result_t 182 | where 183 | F:Plug+Plug+Plug+Unplug, 184 | ::F: Plug + Plug + Plug 185 | { 186 | let cmp = |x|fun2(fun(x)); 187 | Functor::map(cmp, functor) 188 | } 189 | ``` 190 | Like magic, not a single type annotation in sight (in the function body). The function signature is a bit unwieldy again, as we need to convince the compiler that our HKT's `Plug` and `Unplug` traits behave in the way that we would expect application and abstraction in true HKTs to behave (closing our loops again). 191 | 192 | As for `Applicative`, here's one I made earlier: 193 | ```rust 194 | pub trait Applicative: Functor { 195 | fn pure(s:::A) -> Self; 196 | fn app( 197 | f:>::result_t, 198 | s:Self 199 | ) -> >::result_t 200 | where 201 | F:FnMut(::A) -> B + Clone, 202 | Self:Plug + Plug + Unplug, 203 | >::result_t: 204 | Unplug::F,A=F> + 205 | Plug + 206 | Clone, 207 | ::F:Plug 208 | ; 209 | } 210 | ``` 211 | This one took me a little while to get typechecking. Thankfully, most of the type-astronomy is done in the trait, leaving the impl easier to understand and/or implement. 212 | ```rust 213 | impl Applicative for Concrete,A> { 214 | fn pure(a:A) -> Self { 215 | Concrete::of(vec![a]) 216 | } 217 | fn app( 218 | fs:>::result_t, 219 | s:Self 220 | ) -> >::result_t 221 | where 222 | F:FnMut(::A) -> B + Clone, 223 | >::result_t: Clone, 224 | { 225 | let flat:Vec = 226 | Functor::map(|x| 227 | Functor::map(|f| 228 | f.clone()(x.clone()), 229 | fs.clone()), 230 | s) 231 | .unwrap 232 | .into_iter() 233 | .map(|x|x.unwrap) 234 | .flatten() 235 | .collect(); 236 | Concrete::of(flat) 237 | } 238 | } 239 | ``` 240 | Finally, to round things off, we define a true `Monad` in Rust, complete with type inference and checking (provided you have a suitable signature to hint to the compiler) 241 | ```rust 242 | pub trait Monad : Applicative { 243 | fn bind(f:F, s:Self) -> >::result_t 244 | where 245 | Self:Plug+Plug, 246 | F:FnMut(::A) -> 247 | >::result_t + Clone 248 | ; 249 | } 250 | 251 | impl Monad for Concrete,A> { 252 | fn bind(f:F, s:Self) -> >::result_t 253 | where 254 | F:FnMut(::A) 255 | -> >::result_t + Clone 256 | { 257 | let res:Vec = 258 | s.unwrap 259 | .into_iter() 260 | .map(|x|f.clone()(x).unwrap) 261 | .flatten().collect(); 262 | Concrete::of(res) 263 | } 264 | } 265 | ``` 266 | --------------------------------------------------------------------------------