-- # Three-dimensional vectors -- -- This example shows how we might define a module type for -- programming with vectors in normal 3D space. module type vspace_3d = { -- We keep the type of vector components abstract, because it will be -- useful to have the same interface for vectors with components that -- are `f32`, `f64`, or something even more exotic. type scalar -- However, the vector type itself is opaque. It must be a record -- with `x`/`y`/`z` fields. type vector = {x: scalar, y: scalar, z: scalar} -- Vectors must support the usual binary operations. val +: vector -> vector -> vector val -: vector -> vector -> vector val *: vector -> vector -> vector val /: vector -> vector -> vector -- As well as dot products and cross products. val dot: vector -> vector -> scalar val cross: vector -> vector -> vector -- Vectors can be scaled by a scalar. val scale: scalar -> vector -> vector -- We can also take the length of a vector, as well as normalise a -- vector to have unit length. val length: vector -> scalar val normalise: vector -> vector -- Finally, vectors can be rotated around the three axes. val rot_x : (radians: scalar) -> vector -> vector val rot_y : (radians: scalar) -> vector -> vector val rot_z : (radians: scalar) -> vector -> vector } -- So for which scalars can this module type be implemented? We -- describe this with yet another module type. module type scalar = { type t val +: t -> t -> t val -: t -> t -> t val *: t -> t -> t val /: t -> t -> t val f64: f64 -> t val sqrt : t -> t val cos : t -> t val sin : t -> t } -- Because of Futhark's structural type system, the built-in modules -- `f32` and `f64` already satisfy this interface. -- -- We can now define a parametric module for constructing vector -- spaces: module mk_vspace_3d(P: scalar): vspace_3d with scalar = P.t = { type scalar = P.t type vector = {x: scalar, y: scalar, z: scalar} def zero = {x = P.f64 0, y = P.f64 0, z = P.f64 0} def one = P.f64 1 -- Most of the definitions are straight out of a textbook, and so we -- won't be providing much commentary. -- -- We start out by defining two helper functions for doing operations -- on the components of a vector. def map (f: scalar -> scalar) (v : vector) = {x = f v.x, y = f v.y, z = f v.z} def map2 (f: scalar -> scalar -> scalar) (a : vector) (b : vector) = {x = f a.x b.x, y = f a.y b.y, z = f a.z b.z} -- This allows us to conveniently define vector arithmetic and scaling. def (+) = map2 (P.+) def (-) = map2 (P.-) def (*) = map2 (P.*) def (/) = map2 (P./) def scale (s: scalar) = map (s P.*) -- The remaining operations are defined explicitly. def dot (a: vector) (b: vector) = P.(a.x*b.x + a.y*b.y + a.z*b.z) def cross ({x=ax,y=ay,z=az}: vector) ({x=bx,y=by,z=bz}: vector): vector = P.({x=ay*bz-az*by, y=az*bx-ax*bz, z=ax*by-ay*bx}) def length v = P.sqrt (dot v v) def normalise (v: vector): vector = let l = length v in scale (one P./ l) v def rot_x (theta: scalar) ({x,y,z} : vector) = let cos_theta = P.cos theta let sin_theta = P.sin theta in { x , y = P.(cos_theta * y - sin_theta * z) , z = P.(sin_theta * y + cos_theta * z)} def rot_y (theta: scalar) ({x,y,z} : vector) = let cos_theta = P.cos theta let sin_theta = P.sin theta in { x = P.(cos_theta * x - sin_theta * z) , y , z = P.(sin_theta * x + cos_theta * z)} def rot_z (theta: scalar) ({x,y,z} : vector) = let cos_theta = P.cos theta let sin_theta = P.sin theta in { x = P.(cos_theta * x - sin_theta * y) , y = P.(sin_theta * x + cos_theta * y) , z} } -- Instantiating the module: module f64_3d = mk_vspace_3d f64 -- And trying it out: -- ``` -- > f64_3d.cross {x=1,y=2,z=3} {x=4,y=5,z=6} -- {x = -3.0f64, y = 6.0f64, z = -3.0f64} -- ``` -- # See also -- -- [Dex: Multi-step ray tracer](dex-raytrace.html) -- -- The [vector](https://github.com/athas/vector) package.