6 ms·
Please, please, please don't associate inlining everything with functional programming. Lambda-lift and name those functions. If you name them, you can ditch mo
by T-R 10y ago
Please, please, please don't associate inlining everything with functional programming. Lambda-lift and name those functions. If you name them, you can ditch most of those comments, too (by moving that information into the names). Not too familiar with Rust syntax, but (to rewrite without changing semantics) something like:
let open_file = |fname| (fname.as_str(), fs::File::open(fname.as_str()) );
let check_err = |(fname, f)| {
f.and_then(|f| Ok((fname, f)))
.expect(&format!("input file {} could not be opened", fname)) };
let to_buff_reader = |(fname, f)| (fname, io::BufReader::new(f));
let line_to_words = |line| {
line.unwrap()
.split_whitespace()
.map(|w| w.to_string())
.collect::<Vec<_>>().into_iter() };
let get_file_words = |(f, file)| {
file.lines()
.flat_map(line_to_words)
.collect::<HashSet<_>>().into_iter() // prune duplicates
.map(move |word| (word, f)) }; // emit inverted index entry
let add_to_index = |mut idx, (word, f)| {
idx.entry(word).or_insert(Vec::new()).push(f);
idx };
let idx = args.iter()
.map( compose(to_buff_reader, check_err, open_file) ) //[1]
.flat_map(get_file_words)
.fold(HashMap::new(), add_to_index);
[1] Does Rust have a compose function? I hope so. If not, you may need 3 successive "map"s. And stream fusion.
- lifthrasiir 10y ago> Does Rust have a compose function? I hope so. Rust itself does not have a compose function. But a closure works fine, so `|fname| to_buff_reader(check_err(open_file(fname)))` would work. If you want to go further, a nightly Rust allows for this kind of construction: #![feature(unboxed_closures, fn_traits)] // that's why we cannot use stable (yet) struct Composed<F, G>(pub F, pub G); impl<F, G, T, U, V> FnOnce<T> for Composed<F, G> where F: FnOnce<T, Output=U>, G: FnOnce<(U,), Output=V> { type Output = V; extern "rust-call" fn call_once(self, args: T) -> V { self.1(self.0.call_once(args)) } } impl<F, G, T, U, V> FnMut<T> for Composed<F, G> where F: FnMut<T, Output=U>, G: FnMut<(U,), Output=V> { extern "rust-call" fn call_mut(&mut self, args: T) -> V { self.1(self.0.call_mut(args)) } } impl<F, G, T, U, V> Fn<T> for Composed<F, G> where F: Fn<T, Output=U>, G: Fn<(U,), Output=V> { extern "rust-call" fn call(&self, args: T) -> V { self.1(self.0.call(args)) } } fn main() { println!("{}", Composed(|x| x+3, |y| y*4)(5)); // 32 }
- T-R 10y ago> a closure works fine, so `|fname| to_buff_reader(check_err(open_file(fname)))` would work Ah, yes it would. Clearly I'm a bit too tired to be writing code, if I've overlooked function application. I suppose it doesn't have to be point-free. =) Pretty cool, though - thanks for the info.
- yoklov 10y agoRust is on my to-learn list (and I'll likely have to learn it for work anyway) but dear god what a horror. That is firmly in the same echelon as C++ template madness.
- lifthrasiir 10y agoWhile you can do lots of horrible things with generics (cough typenum [1] cough), it has a rather strict rule that prevents it from ever having the same degree of freedom as C++ template has. In particular, this entire thing is correctly type-checked and you won't see hacks like SFINAE. Probably things like this should be encapsulated in a separate crate, reviewed and maintained by the community. [1] http://paholg.com/typenum/typenum/index.html http://paholg.com/typenum/typenum/index.html
- tatterdemalion 10y agoI guarantee you its a far cry from what templates will let you do. However, its overloading a custom type to behave like a higher order function, which is a fairly complex piece of code. Most uses of generics are much simpler.
- discreteevent 10y agoYou are correct to point this out (not to associate inlining everything with FP). However a lot of functional code seems to use this style. Maybe people need to point this out more frequently and raise it in code reviews. To me it's like really bad academic English. The kind that uses the passive voice a lot and uses phrases like "the former" and "the latter", instead of naming things and using short clear sentences. Sometimes I'm convinced that the author of the code, if they were honest, would admit that they had difficulty keeping track of exactly what they were referring to while they were writing it.
- T-R 10y agoOh, it definitely pops up a fair bit, but it really is just not-well-factored code. It's a direct parallel to having long boolean expressions, or long equations without breaking out any sub-expressions and storing them in named variables - after all, functions are just sub-expressions - which is something Code Complete specifically advocated against in procedural/OOP code. Some people seem to fall back into it a bit when they discover point-free style (and that seems to make up a lot of what you see in mixed-paradigm code). I don't think it'd be controversial to say it's bad practice in any paradigm, so the association of it with FP is kind of like judging web programming by late 90's beginner PHP code (which, at one point in time, did describe a lot of web programming, but it was never good, and we'd like to put that behind us).
- kzrdude 10y agoClosure type inference doesn't work well enough in Rust, so your refactoring will not compile unfortunately. Rust needs the closures to be used inline.