From e26b528464d480b523983a950414dd516c883bdd Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Tue, 31 Mar 2020 16:57:58 -0400 Subject: [PATCH] Some cleanliness cleanups --- README.md | 5 +- src/examples.rs | 50 ++++---- src/lib.rs | 1 - src/scratch.rs | 297 ------------------------------------------------ 4 files changed, 29 insertions(+), 324 deletions(-) delete mode 100644 src/scratch.rs diff --git a/README.md b/README.md index 031ab5b..4e57c48 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,14 @@ ## Highest priority: - If my `closure_try2` branch seems to be working: start converting - other things and cleaning everything up. (`twist` is still ugly.) + other things and cleaning everything up. (`twist` is still ugly. + Look at all my TODOs in it.) - See `automata_scratch/examples.py` and implement some of the tougher examples. - `spiral_nested_2` & `spiral_nested_3` (how to compose efficiently?) - `twisty_torus` - - `ram_horn_branch` - how do I pass depth in order to do this right? + - `ram_horn_branch` - Can I pass depth via a closure? ## Important but less critical: diff --git a/src/examples.rs b/src/examples.rs index 39368ea..51eafc8 100644 --- a/src/examples.rs +++ b/src/examples.rs @@ -2,11 +2,10 @@ use std::rc::Rc; use nalgebra::*; //pub mod examples; -use crate::openmesh::{OpenMesh, Tag, Mat4, Vertex, vertex, transform}; +use crate::openmesh::{OpenMesh, Mat4, vertex, transform}; use crate::rule::{Rule, RuleFn, RuleEval, Child}; use crate::prim; use crate::util; -use crate::scratch; fn cube_thing() -> Rule { @@ -369,12 +368,14 @@ fn twist(f: f32, subdiv: usize) -> Rule { // TODO: Clean this code up. It was a very naive conversion from // the non-closure version. let xf = geometry::Rotation3::from_axis_angle(&Vector3::x_axis(), -0.7).to_homogeneous(); - let seed = transform(&vec![ - vertex(-0.5, 0.0, -0.5), - vertex( 0.5, 0.0, -0.5), - vertex( 0.5, 0.0, 0.5), - vertex(-0.5, 0.0, 0.5), - ], &xf); + let seed = { + let s = vec![vertex(-0.5, 0.0, -0.5), + vertex( 0.5, 0.0, -0.5), + vertex( 0.5, 0.0, 0.5), + vertex(-0.5, 0.0, 0.5)]; + util::subdivide_cycle(&transform(&s, &xf), subdiv) + }; + let n = seed.len(); let dx0: f32 = 1.5; let dy: f32 = 0.1/f; let ang: f32 = 0.05/f; @@ -390,23 +391,24 @@ fn twist(f: f32, subdiv: usize) -> Rule { let incr_outer = geometry::Translation3::new(-dx0*2.0, 0.0, 0.0).to_homogeneous() * geometry::Rotation3::from_axis_angle(&y, ang/2.0).to_homogeneous() * geometry::Translation3::new(dx0*2.0, dy, 0.0).to_homogeneous(); + // TODO: Cleanliness fix - transforms? let seed2 = seed.clone(); // TODO: Why do I need the above? let recur = move |incr: Mat4| -> RuleFn { - let seed_orig = transform(&seed2, &incr); - let seed_sub = util::subdivide_cycle(&seed_orig, subdiv); - let n = seed_sub.len(); + let seed_next = transform(&seed2, &incr); + // TODO: Cleanliness fix - utility function to make a zigzag mesh? let geom = OpenMesh { - verts: seed_sub.clone(), + verts: seed_next.clone(), faces: util::parallel_zigzag_faces(n), }; - let (vc, faces) = util::connect_convex(&seed_sub, true); + // TODO: Cleanliness fix - why not just make these return meshes? + let (vc, faces) = util::connect_convex(&seed_next, true); let final_geom = OpenMesh { verts: vec![vc], - faces: faces.clone(), + faces: faces, }; let c = move |self_: Rc| -> RuleEval { @@ -427,30 +429,30 @@ fn twist(f: f32, subdiv: usize) -> Rule { }; Box::new(c) }; + // TODO: Can a macro do anything to clean up some of the + // repetition with HOFs & closures? // TODO: so there's incr_inner & incr_outer that I wanted to // parametrize over. why is it so ugly to do so? - let start = move |self_: Rc| -> RuleEval { + let start = move |_| -> RuleEval { let xform = |dx, i, ang0, div| -> Mat4 { (geometry::Rotation3::from_axis_angle(&y, ang0 + (qtr / div * (i as f32))).to_homogeneous() * geometry::Translation3::new(dx, 0.0, 0.0).to_homogeneous()) }; + // TODO: Cleanliness fix - transforms? - let make_child = |i, incr, xform| -> (OpenMesh, Child) { - - let seed_orig = transform(&seed, &incr); - let seed_sub = util::subdivide_cycle(&seed_orig, subdiv); - let n = seed_sub.len(); + let make_child = |incr, xform| -> (OpenMesh, Child) { let c = Child { rule: Rc::new(Rule { eval: (recur.clone())(incr) }), + // TODO: Cleanliness fix - can macros clean up above? xf: xform, vmap: (0..(n+1)).collect(), - // N.B. n+1, not n. the +1 is for the centroid below + // N.B. n+1, not n. the +1 is for the centroid below. }; - let mut vs = transform(&seed_sub, &xform); + let mut vs = transform(&seed, &xform); // and in the process, generate faces for these seeds: let (centroid, f) = util::connect_convex(&vs, false); vs.push(centroid); @@ -458,8 +460,8 @@ fn twist(f: f32, subdiv: usize) -> Rule { }; // Generate 'count' children, shifted/rotated differently: - let children_inner = (0..count).map(|i| make_child(i, incr_inner, xform(dx0, i, 0.0, 1.0))); - let children_outer = (0..count).map(|i| make_child(i, incr_outer, xform(dx0*2.0, i, qtr/2.0, 2.0))); + let children_inner = (0..count).map(|i| make_child(incr_inner, xform(dx0, i, 0.0, 1.0))); + let children_outer = (0..count).map(|i| make_child(incr_outer, xform(dx0*2.0, i, qtr/2.0, 2.0))); RuleEval::from_pairs( children_inner.chain(children_outer), prim::empty_mesh()) diff --git a/src/lib.rs b/src/lib.rs index 2308825..32daf02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -pub mod scratch; pub mod examples; pub mod openmesh; pub mod rule; diff --git a/src/scratch.rs b/src/scratch.rs deleted file mode 100644 index be5407f..0000000 --- a/src/scratch.rs +++ /dev/null @@ -1,297 +0,0 @@ -use std::rc::Rc; - -/* -struct R<'a> { - b: &'a dyn Fn() -> R<'a>, -} - -#[derive(Copy, Clone)] -struct Foo {} -impl<'a> Foo { - // These are valid, but not especially useful (if I am - // transferring ownership then I cannot have any branching): - fn fn1(self) -> R<'a> { - R { b: & move || self.fn1() } - } - fn fn2(self) -> R<'a> { - R { b: &|| self.fn2() } - } -} -*/ - -// Below (using box instead of a trait object) follows similar rules: -struct S<'a> { - b: Box S<'a>>, -} -#[derive(Copy, Clone)] -struct Foo2 {} -impl<'a> Foo2 { - fn fn1(self) -> S<'a> { - S { b: Box::new(move || self.fn1()) } - } - // Not valid (error[E0373]: closure may outlive the current - // function, but it borrows `self`, which is owned by the current - // function): - //fn fn2(self) -> S<'a> { - // S { b: Box::new(|| self.fn2()) } - //} - // Not valid: - //fn fn3(&self) -> S<'a> { - // S { b: Box::new(move || self.fn3()) } - //} - // Not valid: - //fn fn4(&self) -> S<'a> { - // S { b: Box::new(|| self.fn4()) } - //} -} - -struct T<'a> { - b: Rc T<'a> + 'a>, -} -#[derive(Copy, Clone)] -struct Foo3 {} -impl<'a> Foo3 { - fn fn1(self) -> T<'a> { - T { b: Rc::new(move || self.fn1()) } - } - // Not valid (E0373): - //fn fn2(self) -> T<'a> { - // T { b: Rc::new(|| self.fn2()) } - //} - // Not valid: - //fn fn3(&self) -> T<'a> { - // T { b: Rc::new(move || self.fn3()) } - //} - // Not valid: - //fn fn4(&self) -> T<'a> { - // T { b: Rc::new(|| self.fn4()) } - //} - // But this is now valid because T can be cloned: - fn fn5(self) -> (T<'a>, T<'a>) { - let p = Rc::new(move || self.fn1()); - let p2 = p.clone(); - (T { b: p }, T { b: p2 }) - } -} - -// Further, this is now valid too (lifetimes removed): -struct U { - b: Rc U>, -} -#[derive(Copy, Clone)] -struct Foo4 {} -impl Foo4 { - fn fn1(self) -> U { - U { b: Rc::new(move || self.fn1()) } - } - fn fn5(self) -> (U, U) { - let p = Rc::new(move || self.fn1()); - let p2 = p.clone(); - (U { b: p }, U { b: p2 }) - } -} - -// I can get rid of Copy/Clone if I use FnOnce: -struct V { - b: Rc V>, -} -struct Foo5 {} -impl Foo5 { - fn fn1(self) -> V { - V { b: Rc::new(move || self.fn1()) } - } - fn fn2(self) -> (V, V) { - let p = Rc::new(move || self.fn1()); - let p2 = p.clone(); - (V { b: p }, V { b: p2 }) - } - // and then either kind is fine: - fn fn3(self) -> V { - V { b: Rc::new(|| self.fn3()) } - } - fn fn4(self) -> (V, V) { - let p = Rc::new(|| self.fn3()); - let p2 = p.clone(); - (V { b: p }, V { b: p2 }) - // but this confuses me a bit. doesn't this then let me call - // an FnOnce... more than once? - } -} - -// This is valid and I can recurse: -struct W { - b: Box W>, -} -struct Foo6 {} -impl Foo6 { - fn fn1(s: &Rc) -> W { - let s2 = Rc::clone(&s); - W { b: Box::new(move || Self::fn1(&s2)) } - } - fn fn2(s: &Rc) -> (W, W) { - let s2 = Rc::clone(&s); - let w2 = W { b: Box::new(move || Self::fn1(&s2)) }; - let s3 = Rc::clone(&s); - let w3 = W { b: Box::new(move || Self::fn1(&s3)) }; - (w2, w3) - } -} - -fn foo6() { - - // Whatever (note that it doesn't automatically do Copy): - struct State { - v: u32, - } - - // Purposely put state somewhere it goes out of scope: - let s = { - let s_orig = State { - v: 105, - }; - Rc::new(s_orig) - }; - /* - let fn1 = |f: &dyn Fn(&dyn Fn() -> W) -> (&dyn Fn() -> W)| -> (&dyn Fn() -> W) { - &(|| -> W { - let s2 = Rc::clone(&s); - W { b: Box::new(move || f(f)) } - }) - }; - - let f2 = fn1(fn1); - */ -} - -fn foo7(t: impl Clone) -> impl Clone { - t.clone() -} - -fn foo7b(t: T) -> T { - t.clone() -} - -fn foo7c(t: T) -> T where T: Clone { - t.clone() -} - -// A simple implementation of the Y Combinator -// λf.(λx.xx)(λx.f(xx)) -// <=> λf.(λx.f(xx))(λx.f(xx)) - -// CREDITS: A better version of the previous code that was posted here, with detailed explanation. -// See and also . - -// A function type that takes its own type as an input is an infinite recursive type. -// We introduce a trait that will allow us to have an input with the same type as self, and break the recursion. -// The input is going to be a trait object that implements the desired function in the interface. -// NOTE: We will be coercing a reference to a closure into this trait object. - -trait Apply { - fn apply(&self, f: &dyn Apply, t: T) -> R; -} - -// In Rust, closures fall into three kinds: FnOnce, FnMut and Fn. -// FnOnce assumed to be able to be called just once if it is not Clone. It is impossible to -// write recursive FnOnce that is not Clone. -// All FnMut are also FnOnce, although you can call them multiple times, they are not allow to -// have a reference to themselves. So it is also not possible to write recursive FnMut closures -// that is not Clone. -// All Fn are also FnMut, and all closures of Fn are also Clone. However, programmers can create -// Fn objects that are not Clone - -// This will work for all Fn objects, not just closures -// And it is a little bit more efficient for Fn closures as it do not clone itself. -impl Apply for F where F: - Fn(&dyn Apply, T) -> R -{ - fn apply(&self, f: &dyn Apply, t: T) -> R { - self(f, t) - - // NOTE: Each letter is an individual symbol. - // (λx.(λy.xxy))(λx.(λy.f(λz.xxz)y))t - // => (λx.xx)(λx.f(xx))t - // => (Yf)t - } -} - -// This works for all closures that is Clone, and those are Fn. -// impl Apply for F where F: FnOnce( &Apply, T ) -> R + Clone { -// fn apply( &self, f: &Apply, t: T ) -> R { -// (self.clone())( f, t ) - -// // If we were to pass in self as f, we get - -// // NOTE: Each letter is an individual symbol. -// // λf.λt.sft -// // => λs.λt.sst [s/f] -// // => λs.ss -// } -// } - -// Before 1.26 we have some limitations and so we need some workarounds. But now impl Trait is stable and we can -// write the following: - -fn y(f:impl Fn(&dyn Fn(T) -> R, T) -> R) -> impl Fn(T) -> R { - move |t| ( - |x: &dyn Apply, y| x.apply(x, y) - ) ( - &|x: &dyn Apply, y| f( - &|z| x.apply(x,z), - y - ), - t - ) -} - -// fn y(f:impl FnOnce(&Fn(T) -> R, T) -> R + Clone) -> impl FnOnce(T) -> R { -// |t| (|x: &Apply,y| x.apply(x,y)) -// (&move |x:&Apply,y| f(&|z| x.apply(x,z), y), t) - -// // NOTE: Each letter is an individual symbol. -// // (λx.(λy.xxy))(λx.(λy.f(λz.xxz)y))t -// // => (λx.xx)(λx.f(xx))t -// // => (Yf)t -// } - -// Previous version removed as they are just hacks when impl Trait is not available. - -fn fac(n: usize) -> usize { - let almost_fac = |f: &dyn Fn(usize) -> usize, x| - if x == 0 { - 1 - } else { - x * f(x - 1) - } - ; - let fac = y( almost_fac ); - fac(n) -} - -fn fib( n: usize ) -> usize { - let almost_fib = |f: &dyn Fn(usize) -> usize, x| - if x < 2 { - 1 - } else { - f(x - 2) + f(x - 1) - }; - let fib = y(almost_fib); - fib(n) -} - -fn optimal_fib( n: usize ) -> usize { - let almost_fib = |f: &dyn Fn((usize,usize,usize)) -> usize, (i0,i1,x)| - match x { - 0 => i0, - 1 => i1, - x => f((i1,i0+i1, x-1)) - } - ; - let fib = |x| y(almost_fib)((1,1,x)); - fib(n) -} - -fn test_y() { - println!("{}", fac(10)); - println!("{}", fib(10)); - println!("{}", optimal_fib(10)); -}