sunjay / turtle

Create Animated Drawings in Rust
http://turtle.rs
Mozilla Public License 2.0
559 stars 54 forks source link

WIP: Automagic number converting #247

Closed enaut closed 2 years ago

enaut commented 2 years ago

It bothers me a lot that you always have to write t.forward(100.0) instead of t.forward(100).

It turns out to be quite straight forward to make the argument type variable as long as it can be converted to f64 using the From trait.

A sample function would be:

pub type Distance = f64;

fn multiply<T>(x:T) -> Distance where Distance: From<T> {
    let n:Distance = Distance::from(x);
    n*n
}

fn main() {
println!("{:?}", multiply(5));
println!("{:?}", multiply(6.0));
println!("{:?}", multiply(7.3));
println!("{:?}", multiply(244u8));
}

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=11144955d3f2b0ae26e924612f905dd9

This PR contains a proof of concept implementation for the square example demonstrating different types as argument to forward and right including a custom struct that implements impl From<CustomStruct> for f64.

Would you appreciate that kind of change? Should I convert the whole turtle code base?

enaut commented 2 years ago

Oh the auto format changed quite a bit too :( - I'd vote for adopting the default formatting of rust-fmt just for consistencies sake...

enaut commented 2 years ago

Fixed the formatting.

For the generics either T can be used or Angle/Distance both are in this PR

sunjay commented 2 years ago

Hi @enaut, thanks for the suggestion and for taking the time to implement it. Using f64 instead of generics is actually an intentional decision we made when we first created the crate. We're trying to keep the core interface of turtle as simple as possible and avoid generics unless they are absolutely necessary. For example, being able to pass in a color as both a string and a tuple wouldn't be possible without generics, so that's why several color functions take a type that implements Into<Color>.

Writing .0 can be a little bit inconvenient (bothers me too sometimes), but it's something we can live with given that it makes the function signatures easier to explain. This crate is aimed at complete beginners to rust, so sometimes we make trade offs like this.

I've actually filed an issue in the rust repo in the past to make the situation better by at least making sure we have a nice the error message for cases like this. My suggestions to allow 100 to be used as both an integer literal and a floating point literal have not been received well. (People thought I was talking about an implicit conversion but actually I just wanted smarter type inference.) So this is just something we'll have to live with for now.

Appreciate you starting this as a proof of concept so we could discuss it. Unfortunately it's not what we're going for with this crate. Thanks again for taking the time.

enaut commented 2 years ago

That is very unfortunate. I used rust turtle in one course at my school and this proved to be an absolute show stopper (especially for the absolute beginners). - Thus I cannot really use rust turtle in the courses. The students know nothing about float and int and do not understand the error message. It is also not the goal to teach them about the differences as there is only enough time to get a rough idea of programming.

Could this decision be open for discussion again?

sunjay commented 2 years ago

Sorry to hear your students had such a bad experience. If the error messages were confusing we should open up issues on the rust repo and try to get that resolved. I really think this is something that needs to be resolved at the language level (if at all), not in the turtle crate itself. If absolute beginners to programming are learning rust and using this crate, we have far bigger hurdles to teach than explaining that numbers need to end in a decimal point in order to avoid errors.

Integers and floating point numbers are not at all interchangeable in a language like Rust. Even if we allow students to write forward(100) instead of forward(100.0), we'll still need to deal with confusion around rounding when one of them inevitably writes forward(1 / 2) and wonders why the turtle isn't moving. Teaching them to always add .0 isn't convenient, but hopefully it does avoid a lot of confusion at the end of the day to have all numbers be the same type.

Again, I empathize with your students. That's why I filed that issue to improve the error message. I just really don't think this is something to be solved at the level of this crate. Supporting all different numeric types is likely to generate different types of confusion and I don't think avoiding .0 is worth it.

enaut commented 2 years ago

Could it be a possibility to add a feature flag? – I agree that in some courses especially at university the difference between number types is essential – however for most turtle applications the difference is nonexistent. Just to stress again: I have said that they have to write .0 on every number. They did, but forgot once. I had that case in every class at least 3 times (with only 10 students). And even when we did more complex things 6 hours into the topic, the students were still hitting that problem.

enaut commented 2 years ago

Mhm another option that is independent from turtle-rs would be to create a wrapper crate. Maybe I'll do that.

sunjay commented 2 years ago

Sorry for the late reply. I was going to suggest some kind of wrapper too. If you want to avoid wrapping the whole turtle type and re-producing all the same methods, you could use an extension trait instead:

pub trait TurtleExt {
    fn fwd<T: Into<f64>>(&mut self, distance: T);
    // ...etc...
}

This requires teaching the students to import that trait every time, so you may prefer the wrapper to this, but I thought I'd offer another option just in case.

I'm still pretty firm in my belief that if .0 was causing problems, we should improve the rustc error messages to make them friendlier, not try and solve that in the turtle crate. Creating an implicit conversion with Into<f64> is likely to lead to other problems (e.g. 1 / 10 = 0 because of integer division, 100 + 50.0 doesn't work even though forward(100) and forward(50.0) both do, etc.). I am not convinced that creating that confusion is worth it just to support simpler cases that can be solved by adding .0.

Hope the wrapper you're thinking of using helps your students! Sorry there isn't a great solution to this.

enaut commented 2 years ago

Mhm I guess this boils down to personal preference.

I don't mind so much to explain the difference between numbers especially when dividing. I think it totally makes sense that a division does weird things - they know that from earlier school years.

But it does not make sense that the turtle cannot go 100 steps but needs to go 100.0 - It does make perfect sense from a programmers point of view but my students are not programmers and most will never be. I always try to reduce the amount of concepts they need to learn in a short amount of time.

enaut commented 2 years ago

Oh and thanks again for helping me out!