|
| 1 | +#![deny(missing_docs)] |
| 2 | + |
| 3 | +//! # Higher Order Core |
| 4 | +//! |
| 5 | +//! This crate contains core structs and traits for programming with higher order data structures. |
| 6 | +//! |
| 7 | +//! ### Introduction to higher order data structures |
| 8 | +//! |
| 9 | +//! A higher order data structure is a generalization of an ordinary data structure. |
| 10 | +//! |
| 11 | +//! In ordinary data structures, the default way of programming is: |
| 12 | +//! |
| 13 | +//! - Use data structures for data |
| 14 | +//! - Use methods/functions for operations on data |
| 15 | +//! |
| 16 | +//! In a higher order data structure, data and functions become the same thing. |
| 17 | +//! |
| 18 | +//! The central idea of an higher order data structure, |
| 19 | +//! is that properties can be functions of the same type. |
| 20 | +//! |
| 21 | +//! For example, a `Point` has an `x`, `y` and `z` property. |
| 22 | +//! In ordinary programming, `x`, `y` and `z` might have the type `f64`. |
| 23 | +//! |
| 24 | +//! If `x`, `y` and `z` are functions from `T -> f64`, |
| 25 | +//! then the point type is `Point<T>`. |
| 26 | +//! |
| 27 | +//! A higher order `Point<T>` can be called, just like a function. |
| 28 | +//! When called as a function, `Point<T>` returns `Point`. |
| 29 | +//! |
| 30 | +//! However, unlike functions, you can still access properties of `Point<T>`. |
| 31 | +//! You can also define methods and overload operators for `Point<T>`. |
| 32 | +//! This means that in a higher order data structure, data and functions become the same thing. |
| 33 | +//! |
| 34 | +//! ### Motivation of programming with higher order data structures |
| 35 | +//! |
| 36 | +//! The major application of higher order data structures is geometry. |
| 37 | +//! |
| 38 | +//! An typical usage is e.g. to create procedurally generated content for games. |
| 39 | +//! |
| 40 | +//! Higher order data structures is about finding the right balance between |
| 41 | +//! hiding implementation details and exposing them for various generic algorithms. |
| 42 | +//! |
| 43 | +//! For example, a unit circle can be thought of as having the type `Point<f64>`. |
| 44 | +//! The argument can be an angle in radians, or a value in the unit interval `[0, 1]`. |
| 45 | +//! |
| 46 | +//! Another example, a line can be thought of as having the type `Point<f64>`. |
| 47 | +//! The argument is a value in the unit interval `[0, 1]`. |
| 48 | +//! When called with `0`, you get the start point of the line. |
| 49 | +//! When called with `1`, you get the end point of the line. |
| 50 | +//! |
| 51 | +//! Instead of declaring a `Circle` type, a `Line` type and so on, |
| 52 | +//! one can use `Point<f64>` to represent both of them. |
| 53 | +//! |
| 54 | +//! Higher order data structures makes easier to write generic algorithms for geometry. |
| 55 | +//! Although it seems abstract at first, it is also practically useful in unexpected cases. |
| 56 | +//! |
| 57 | +//! For example, an animated point can be thought of as having the type `Point<(&[Frame], f64)>`. |
| 58 | +//! The first argument contains the animation data and the second argument is time in seconds. |
| 59 | +//! Properties `x`, `y` and `z` of an animated point determines how the animated time is computed. |
| 60 | +//! The details of the implementation can be hidden from the algorithm that uses animated points. |
| 61 | +//! |
| 62 | +//! Sometimes you need to work with complex geometry. |
| 63 | +//! In these cases, it is easier to work with higher order data structures. |
| 64 | +//! |
| 65 | +//! For example, a planet might have a center, equator, poles, surface etc. |
| 66 | +//! A planet orbits around a star, which orbits around the center of a galaxy. |
| 67 | +//! This means that the properties of a planet, viewed from different reference frames, |
| 68 | +//! are functions of the arguments that determine the reference frame. |
| 69 | +//! You can create a "higher order planet" to reason about a planet's properties |
| 70 | +//! under various reference frames. |
| 71 | +//! |
| 72 | +//! ### Design |
| 73 | +//! |
| 74 | +//! Here is an example of how to declare a new higher order data structure: |
| 75 | +//! |
| 76 | +//! ```rust |
| 77 | +//! use higher_order_core::{Ho, Call, Arg, Func}; |
| 78 | +//! use std::sync::Arc; |
| 79 | +//! |
| 80 | +//! /// Higher order 3D point. |
| 81 | +//! #[derive(Clone)] |
| 82 | +//! pub struct Point<T = ()> where f64: Ho<T> { |
| 83 | +//! /// Function for x-coordinates. |
| 84 | +//! pub x: <f64 as Ho<T>>::Fun, |
| 85 | +//! /// Function for y-coordinates. |
| 86 | +//! pub y: <f64 as Ho<T>>::Fun, |
| 87 | +//! /// Function for z-coordinates. |
| 88 | +//! pub z: <f64 as Ho<T>>::Fun, |
| 89 | +//! } |
| 90 | +//! |
| 91 | +//! // It is common to declare a type alias for functions, e.g: |
| 92 | +//! pub type PointFunc<T> = Point<Arg<T>>; |
| 93 | +//! |
| 94 | +//! // Implement `Ho<Arg<T>>` to allow higher order data structures |
| 95 | +//! // using properties `<Point as Ho<T>>::Fun`. |
| 96 | +//! impl<T: Clone> Ho<Arg<T>> for Point { |
| 97 | +//! type Fun = PointFunc<T>; |
| 98 | +//! } |
| 99 | +//! |
| 100 | +//! // Implement `Call<T>` to allow higher order calls. |
| 101 | +//! impl<T: Copy> Call<T> for Point |
| 102 | +//! where f64: Ho<Arg<T>> + Call<T> |
| 103 | +//! { |
| 104 | +//! fn call(f: &Self::Fun, val: T) -> Point { |
| 105 | +//! Point::<()> { |
| 106 | +//! x: <f64 as Call<T>>::call(&f.x, val), |
| 107 | +//! y: <f64 as Call<T>>::call(&f.y, val), |
| 108 | +//! z: <f64 as Call<T>>::call(&f.z, val), |
| 109 | +//! } |
| 110 | +//! } |
| 111 | +//! } |
| 112 | +//! |
| 113 | +//! impl<T> PointFunc<T> { |
| 114 | +//! /// Helper method for calling value. |
| 115 | +//! pub fn call(&self, val: T) -> Point where T: Copy { |
| 116 | +//! <Point as Call<T>>::call(self, val) |
| 117 | +//! } |
| 118 | +//! } |
| 119 | +//! |
| 120 | +//! // Operations are usually defined as simple traits. |
| 121 | +//! // They look exactly the same as for normal generic programming. |
| 122 | +//! /// Dot operator. |
| 123 | +//! pub trait Dot<Rhs = Self> { |
| 124 | +//! /// The output type. |
| 125 | +//! type Output; |
| 126 | +//! |
| 127 | +//! /// Returns the dot product. |
| 128 | +//! fn dot(self, other: Rhs) -> Self::Output; |
| 129 | +//! } |
| 130 | +//! |
| 131 | +//! // Implement operator once for the ordinary case. |
| 132 | +//! impl Dot for Point { |
| 133 | +//! type Output = f64; |
| 134 | +//! fn dot(self, other: Self) -> f64 { |
| 135 | +//! self.x * other.x + |
| 136 | +//! self.y * other.y + |
| 137 | +//! self.z * other.z |
| 138 | +//! } |
| 139 | +//! } |
| 140 | +//! |
| 141 | +//! // Implement operator once for the higher order case. |
| 142 | +//! impl<T: 'static + Copy> Dot for PointFunc<T> { |
| 143 | +//! type Output = Func<T, f64>; |
| 144 | +//! fn dot(self, other: Self) -> Func<T, f64> { |
| 145 | +//! let ax = self.x; |
| 146 | +//! let ay = self.y; |
| 147 | +//! let az = self.z; |
| 148 | +//! let bx = other.x; |
| 149 | +//! let by = other.y; |
| 150 | +//! let bz = other.z; |
| 151 | +//! Arc::new(move |a| ax(a) * bx(a) + ay(a) * by(a) + az(a) * bz(a)) |
| 152 | +//! } |
| 153 | +//! } |
| 154 | +//! ``` |
| 155 | +//! |
| 156 | +//! To disambiguate impls of e.g. `Point<()>` from `Point<T>`, |
| 157 | +//! an argument type `Arg<T>` is used for point functions: `Point<Arg<T>>`. |
| 158 | +//! |
| 159 | +//! For every higher order type `U` and and argument type `T`, |
| 160 | +//! there is an associated function type `T -> U`. |
| 161 | +//! |
| 162 | +//! For primitive types, e.g. `f64`, the function type is `Func<T, f64>`. |
| 163 | +//! |
| 164 | +//! For higher order structs, e.g. `X<()>`, the function type is `X<Arg<T>>`. |
| 165 | +//! |
| 166 | +//! The code for operators on higher order data structures must be written twice: |
| 167 | +//! |
| 168 | +//! - Once for the ordinary case `X<()>` |
| 169 | +//! - Once for the higher order case `X<Arg<T>>` |
| 170 | +
|
| 171 | +use std::sync::Arc; |
| 172 | + |
| 173 | +/// Standard function type. |
| 174 | +pub type Func<T, U> = Arc<dyn Fn(T) -> U + Send + Sync>; |
| 175 | + |
| 176 | +/// Used to disambiguate impls for Rust's type checker. |
| 177 | +#[derive(Copy, Clone)] |
| 178 | +pub struct Arg<T>(pub T); |
| 179 | + |
| 180 | +/// Implemented by higher order types. |
| 181 | +/// |
| 182 | +/// A higher order type might be a concrete value, |
| 183 | +/// or it might be a function of some input type `T`. |
| 184 | +/// |
| 185 | +/// Each higher order type has an associated function type |
| 186 | +/// for any argument of type `T`. |
| 187 | +/// |
| 188 | +/// This makes it possible to e.g. associate `PointFunc<T>` with `Point`. |
| 189 | +pub trait Ho<T>: Sized { |
| 190 | + /// The function type. |
| 191 | + type Fun: Clone; |
| 192 | +} |
| 193 | + |
| 194 | +/// Implemented by higher order calls. |
| 195 | +pub trait Call<T>: Ho<Arg<T>> { |
| 196 | + /// Calls function with some value. |
| 197 | + fn call(f: &Self::Fun, val: T) -> Self; |
| 198 | +} |
| 199 | + |
| 200 | +impl<T, U> Call<T> for U |
| 201 | +where U: Ho<Arg<T>, Fun = Func<T, Self>> { |
| 202 | + fn call(f: &Self::Fun, val: T) -> Self {f(val)} |
| 203 | +} |
| 204 | + |
| 205 | +impl<T: Clone> Ho<()> for T {type Fun = T;} |
| 206 | + |
| 207 | +impl<T> Ho<Arg<T>> for f64 {type Fun = Func<T, f64>;} |
| 208 | +impl<T> Ho<Arg<T>> for f32 {type Fun = Func<T, f32>;} |
| 209 | +impl<T> Ho<Arg<T>> for u8 {type Fun = Func<T, u8>;} |
| 210 | +impl<T> Ho<Arg<T>> for u16 {type Fun = Func<T, u16>;} |
| 211 | +impl<T> Ho<Arg<T>> for u32 {type Fun = Func<T, u32>;} |
| 212 | +impl<T> Ho<Arg<T>> for u64 {type Fun = Func<T, u64>;} |
| 213 | +impl<T> Ho<Arg<T>> for usize {type Fun = Func<T, usize>;} |
| 214 | +impl<T> Ho<Arg<T>> for i8 {type Fun = Func<T, i8>;} |
| 215 | +impl<T> Ho<Arg<T>> for i16 {type Fun = Func<T, i16>;} |
| 216 | +impl<T> Ho<Arg<T>> for i32 {type Fun = Func<T, i32>;} |
| 217 | +impl<T> Ho<Arg<T>> for i64 {type Fun = Func<T, i64>;} |
| 218 | +impl<T> Ho<Arg<T>> for isize {type Fun = Func<T, isize>;} |
0 commit comments