-
Notifications
You must be signed in to change notification settings - Fork 543
Implement Robot Simulator #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Robot Simulator #146
Conversation
Robot {} | ||
} | ||
|
||
pub fn turn_right(&mut self) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would pre-fill the functions with unimplemented!()
just to make sure, that you know where to put the implementation. This is more important for functions which return values, because unimplemented!()
always typechecks, no matter the type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool. Done. Thanks!
Your test suite will push them to a certain solution in any case. If you set |
As suggested by jonasbb exercism#146 (comment)
Ah, right, if I remove the fn turn_right(&self) -> Self {
Robot::New{...}
} Which might be interesting. I don't think any of our exercises take that approach. |
As mentioned here: exercism#146 (comment) I like the idea of pushing towards a solution with immutable robots. It's a different way of using structs than our other exercises do. As a side benefit, it shortens the test functions. Students can always edit the test file or the stub file, if they so wish. So they could still implement mutable robots, though I don't think many (or any, probably) will do so.
Two tweaks:
|
I'm unsure what the Currently the Robot But when getting the
But my instinct has been wrong a lot. |
usually, accessor methods return |
Ooh, my instinct was right for once. Go team instinct! 👏 |
What about primitives, @steveklabnik ? For example, given the struct struct Robot {
x: isize,
y: isize
} Would you expect a fn postion(&self) -> (isize, isize) or fn postion(&self) -> (&isize, &isize) Alternatively we could create a whole new Position type, like we did with queen attack |
If the type is |
That's what I was thinking as well. I'm clearly on a roll today. |
As discussed exercism#146 (comment) and exercism#146 (comment) Per Steve Klabnik > usually, accessor methods return &T. If they returned T, then they > would destroy the owner, or you'd be making a clone every time
Taking by value and returning a copy just doesn't feel rusty-y to me. I expect functions to take the most general type of arguments they can, |
Working example in place. Now to figure out where to place this problem. The solution will involve:
And, if the student chooses not to clone the I'm thinking it should go around |
So what API are you proposing, @Dr-Emann? |
I would expect |
That was the initial implementation. And it might be the right one. But I'm leaning towards immutable because
I figure if I don't expect immutability then other programmers with an OO-focused background probably don't expect immutability. So a problem that forces immutability may make me (and them) think about immutability. Which I think is good. Immutability brings other benefits, though they aren't exposed by the tests. It would be very easy to trace your Robot's path, for example. Re-winding to a specific point on the path would be trivial. All very nice, but not part of the test suite. |
As per: exercism#146 (comment) > The solution will involve: > > Enums (though the stub file provides them) > Structs (either a tuple struct, or a normal struct) > Some matching on Direction variant > Iteration > > And, if the student chooses not to clone the Direction, they may end up > in a world of Lifetimes. That, though, is optional. > > I'm thinking it should go around grade-school. It's got a pretty > straightforward use of enums, so it should come before Allergies (which > has slightly more complex enum usage). But beyond enums, students will > be familiar with all the tools they need to solve this by the time they > reach grade-school
Ok. This now passes CI and is basically ready to go. Once we settle on API and improve the example code a bit, it'll be ready to merge. |
Thinking about the immutability thing more... As I mentioned earlier, I think the immutable design advantages become clear if you want to rewind to a point. Maybe we could have an optional test that's commented out: // Optional:
// #[test]
// fn robot_simulator_can_return_robot_at_any_point() {
// let robot = Robot::new(...);
// let simulator = RobotSimulator::new(robot);
// simulator.simulate("LAAAR");
// assert_eq!((-1,2), simulator.at_step(4).robot_position);
// assert_eq!((0,2), simulator.at_step(3).robot_position);
// etc.
// } |
I think this is now ready. Leaving the PR open until Tuesday, July 5th, after which I'll merge if there are no issues. |
@IanWhitney Is the intention for the example implementation to be showing idiomatic rust, or just a potential solution to pass the tests? |
@Ryman That is a good question without a clear answer! Track maintainers discussed this recently: exercism/discussions#27 Unsurprisingly, people had different opinions. I think everyone is agreed that the first job of the example code is to pass the tests (otherwise our CI fails). Beyond that first requirement, there was general agreement (though not unanimous) that the code should be:
Probably my favorite description of the example code was this one:
Which I most certainly am. I ended up writing Rust exercises because I wanted to use Exercism to help me learn Rust. My example code for this exercise is probably pretty close to what I would submit if I did this exercise. |
That's not to say my example code is exactly idiomatic, though. I'm still quite new to Rust and am learning new stuff all the time. Which is one of the reasons I encourage discussion in these PRs. |
} | ||
} | ||
|
||
pub fn turn_right(self) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll not comment on whether we want to go with mutable or immutable interface.
But I want to say that if we want an immutable interface, I suspect we can do turn_right(&self) -> Self
instead of turn_right(self) -> Self
. Can you check?
(And if it were mutable, it would be turn_right(&mut self)
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That results in compiler errors. Can not move out of borrowed content. There are additional changes we'd need to make to get that to work, though I don't know what they are off the top of my head.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment surprised me enough that I checked out your branch to take a look.
My diagnosis: Make your Position
#[derive(Clone, Copy)]
and it will work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same with advance
and instructions
- If your Direction
is Copy
then they can take &self
Can't do the same for execute
though since it can potentially return self
, which means a &self
wouldn't be able to become a Self
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh. I never would have guessed that from the compiler error. Clearly.
Based on this quote from The Book, I'll update these to take &self
We should default to using &self, as you should prefer borrowing over taking ownership, as well as taking immutable references over mutable ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well, to be fair, making them Copy
was not the only solution - could have only made them Clone
, not Copy
, and then explicitly call clone()
on them. But I suggested Copy
simply because https://doc.rust-lang.org/std/marker/trait.Copy.html said "Generally speaking, if your type can implement Copy
, it should"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't always do this myself (maybe I should if I run into a compiler output I don't understand), but rustc should have outputted src/lib.rs:67:21: 67:25 help: run
rustc --explain E0507 to see a detailed explanation
And I found that one of the solutions rustc --explain E0507
does say you can make it Copy
, so that could have been a way to understand.
As @petertseng pointed out, I can update these functions to borrow `self` if I give Direction and Position `Copy` exercism#146 (comment)
} | ||
|
||
pub fn instructions(&self, instructions: &str) -> Self { | ||
instructions.chars().fold(Self::build(self.position, self.direction), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't tested, but if you had Robot
derive Clone
, I think this Self::build(...)
could just be clone()
instead. But I don't see this as a change that has to be done.
9bc26e9
to
53db50e
Compare
For me, I think I've said all I want to say 👍 |
|
||
impl Robot { | ||
#[allow(unused_variables)] | ||
pub fn new(x: isize, y: isize, d: &Direction) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Direction
argument being a reference here is a bit out of place when compared to the rest of the interface, for if we assume the user may want to store &Direction
in their Robot
type, the other methods will not allow this to be updated to anything reasonable, i.e.:
fn turn_right(&self) -> Self {
Robot {
position: self.position,
// nowhere to tie this lifetime to as it's not an input
direction: &self.direction.next_clockwise()
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's preferred for structs to take ownership of their values, so I'm OK with pushing students towards that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear, that means fn new(x: isize, y: isize, d: Direction) -> Self
rather than the current signature with the push towards the student calling clone
to take ownership?
Sorry for the delayed response, I left some feedback on things which may affect the users code and just left some |
As suggested by Ryman exercism#146 (comment)
Unless there's more discussion on this, I'll merge tonight. |
Tests come from https://github.com/exercism/x-common/blob/183934754b1847612809db410a8aeb640678f188/robot-simulator.json As discussed in exercism#117 we are providing a stub implementation so that ignored tests do not fail when a student hasn't yet implemented the functionality they exercise. A lot of excellent feedback and help provided by - jonasbb - petertseng - steveklabnik - Ryman - Dr-Emann Full details of the design discussion at exercism#146 -- Some details that people may care about: ---- Robots are immutable because: - Immutability is the default in Rust - No other problem (that I know of) really features Rust's default immutability - Immutability is not my natural inclination I figure if I don't expect immutability then other programmers with an OO-focused background probably don't expect immutability. So a problem that forces immutability may make me (and them) think about immutability. Which I think is good. Immutability brings other benefits, though they aren't exposed by the tests. It would be very easy to trace your Robot's path, for example. Re-winding to a specific point on the path would be trivial. All very nice, but not part of the test suite. ---- The example code has the `build` function because: There's still some awkwardness around the `new` function, since it takes x/y and all the internals use Position. I have to preserve the `new(x, y, direction)` function signature because of the tests (and because the tests should follow the example of Queen Attack, which also passes `x` and `y` in as separate parameters. But that function becomes a pain once I'm working inside the Robot and I have a Position. The new `build` function creates a robot using a Position and Direction, so it can be easily used by all of the Robot's internals. And `new` now just wraps around `build`. This is kind of the exact opposite way I'd normally do this. I'd expect a `build` function to be the public API, do the data manipulation and then call a private `new` function. But if I were to do that here then all of the tests would contain: ``` Robot::build(x, y, &direction) ``` Which I think would be non-idiomatic and weird to students. ----
f6827d6
to
9129c83
Compare
And in it goes. Thanks for all your input! |
Implement Robot Simulator
This follows the current standard test suite
The stub file, as we discussed in #117, is just enough to run
cargo test
and not get any errors from ignored tests.