-
Notifications
You must be signed in to change notification settings - Fork 543
Implement Triangle #197
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 Triangle #197
Conversation
Err(()) | ||
} else { | ||
let et = EquilateralTriangle::build(sides); | ||
if et.is_ok() { |
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.
consider an if let
here to avoid an unwrap
below.
Well, this is just speaking about the example. The API imposed by the test does not seem to require boxes at all (unless I miss something). It's very possible that the student will simply have |
I'm trying to reason through the advantages and disadvantages of having the api be As written:
Since we are trying to decide whether the API is sane, please add any thoughts you can think of. I'll sleep on what I've got for now. |
The logic is all the same, so the only difference is whether the type becomes generic, right? It seems like this may depends on where we decide to place this problem. Any other considerations? I can be convinced either way. |
Yes, with this API a student could do everything in the Triangle struct. No need for enums/boxes/etc. Something like: fn build(sides: [u16;3]) -> Result<Triangle, ()> {
let equilateral_test = //...;
let isoscoles_test = //...;
let scalene_test = //...;
Ok(Triangle { equilateral: equilateral_test, //... }); (or, just create a Triangle struct with I think this implementation is lacking. Maybe I'm wrong. There's no test in the current test suite that demonstrates any problems with this implementation. If we wanted to force an implementation, we could write some test functions that require specific triangle types. Say a function that checks for Golden Triangleness and that only accepts Isosceles. If we did come up with tests like this, I would prefer to do one of two things:
|
As for a Triangle enum, it would work. I tried that approach for a bit but changed tack. Partly because I'm just not a fan of Enums, and partly because I think they are a bad fit for the concept of Triangles. For something that has a clear and distinct set of options enums can be great. But there are other ways of classifying triangles that makes Enum a bad choice. Say you went with this enum: enum Triangle {
Equilateral,
Isosceles,
Scalene
} And then you want to write |
The main hurdle for me is making the code generic over numeric types. Particularly the |
This also leads us to the question of: If we write a stub file (I notice none currently is), what is the signature we put in it?
What woud you say is lacking? But there are some implementation details that probably cannot be tested. (I'm thinking of the sieve problem, where you can't really test that the implementation uses the sieve algorithm, only that it gives you a list of primes)
Equilateral, Isosceles, and Scalene are the only options for the relationship between the sides, so to me this does tell me that enum is a great fit. And I would have another enum for right/obtuse/acute telling the relationship between the angles, since again a triangle can only be exactly one of these. So enum is again a great fit for that.
You can't write a function on values, but you can write a function on types. enum Number {
Zero,
Succ(Box<Number>),
}
impl Number {
fn is_zero(&self) -> bool {
match *self {
Number::Zero => true,
_ => false,
}
}
}
fn main() {
println!("{}", Number::Zero.is_zero());
println!("{}", Number::Succ(Box::new(Number::Zero)).is_zero());
} |
However, despite my assertion that enums are a great fit for isosceles vs equilateral vs scalene, that says nothing about whether the tests need to require enums (they don't, by the way). So effectively I'm discussing how I would implement it, rather than how it's to be tested, and how I would implement it is less important here (that's for discussion on the site when I submit an implementation, not for this PR) There is no need to change what's being tested right now, I don't think. I can be convinced otherwise, of course. |
I tried. Indeed, it seems difficult. Attempting without
Okay, what about... fn valid_sides<T: PartialOrd + std::ops::Add>(sides: [T; 3]) -> bool
where <T as std::ops::Add>::Output: PartialOrd<T> {
// omitted for now
} Ah, not quite.
Okay, maybe it works if I make it copy...
This works but notice I had to omit the zero check because I can't get a zero of T. |
Okay, now this type signature's just getting ridiculous. fn valid_sides<T: Copy + PartialOrd + std::ops::Add + std::ops::Sub>(sides: [T; 3]) -> bool
where <T as std::ops::Add>::Output: PartialOrd<T>, <T as std::ops::Sub>::Output: PartialOrd<T> {
let zero = sides[0] - sides[0];
( sides.iter().all(|&s| zero < s) )
&& ( sides[0] + sides[1] >= sides[2] )
&& ( sides[1] + sides[2] >= sides[0] )
&& ( sides[2] + sides[0] >= sides[1] )
} But it works. #[cfg(test)]
mod tests {
#[test]
fn valid_uint() {
let sides = [4, 3, 2];
assert!(super::Triangle::valid_sides(sides));
}
#[test]
fn uint_zero() {
let sides = [0, 3, 2];
assert!(!super::Triangle::valid_sides(sides));
}
#[test]
fn nope_uint() {
let sides = [7, 3, 2];
assert!(!super::Triangle::valid_sides(sides));
}
#[test]
fn valid_floats() {
let sides = [0.4, 0.3, 0.2];
assert!(super::Triangle::valid_sides(sides));
}
} I imagine that num can make this easier by giving us a zero for the type instead of having to synthesize one. I'm not sure it's worth adding to the Cargo.toml that everyone will get though? |
And now I understand why the I think if we include floats, we should include |
Yeah makes sense. What was that problem we didn't end up adding the ... ah, it was space age, because Think it's useful enough to introduce to students? My opinion has to be taken with a grain of salt since I'm pretty unknowledgeable about |
I'll try using |
Oh, fun, all of my Triangle tests rely on Ord. And floats don't have Ord |
To do:
|
I don't suppose it makes much of a difference, but what if maybe the float-compatible example was put into Or what if it were just made the default (and only) example? |
If I make it the only example, then I have to include |
Thoughts on placement & topics covered? My example is not the way most students will go, I suspect. This works, right? struct Triangle {
sides: [u16;3]
}
impl Triangle {
pub fn is_equilateral(&self) -> bool {
//test sides for equality
}
//and so on
} So if most students will go with an implementation like that, then we should place the problem around |
I imagine a single struct will be a common approach. I am having a hard time to think about topic. Math seems certain. Depending on implementation, enums or traits are potential topics. Basically what' already been said in HINTS. I'm feeling indecisive about where to place, since I have a large range that I would have found acceptable: anywhere after leap, but before grade-school. Since our suggestions intersect and yours is smaller, take your pick? Before I forget, another test was added in exercism/problem-specifications#364 after work on this was started. |
My example solution is probably overkill, we're putting this in a place where a more straightforward solution will be accessible to students. exercism#197 (comment)
Since nothing in the tests suggests my weird-o implementation, I also plan on replacing the example code with a solution closer to what we expect students to know at this point in the track. |
Examples now simplified. I think we're wrapping this one up. Let me know. |
|
||
#[test] | ||
#[ignore] | ||
fn scalene_traingle_has_no_equal_sides_one() { |
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 think for all four of scalene tests you spelled it as traingle
Another last thought I had: |
I thought it was idiomatic for |
I reviewed a few examples and find this to be true for all examples I saw, with no counterexamples. I could maybe search the Rust standard libs, but it would take a while to sift through them all looking for any counterexample. Probably can keep it as is then. |
Test suite mostly follows the standard. I've started off with a couple of Result-checking tests, just to establish that we expect a Result back. Floating-point tests are included as optional, and an example that passes those tests is included. Making a function generic over Ints & Floats is tricky, and we decided to not make it a requirement. Hints file points to some optional ways of implementing a solution. A single `Triangle` struct gets the test passing fastest, but other designs can be explored. Since the single struct approach will be accessible to students by the time they reach rna-transcription, we've put it there. If we added tests that forced an Enum/Trait/Etc. impl, then we'd move it. But tests like that seemed too proscriptive. Full discussion of the implementation of this exercise is at exercism#197
356e6d9
to
3e28089
Compare
Rebased down to a single commit. |
Future reference, @petertseng : Generic over Float & Int without http://exercism.io/submissions/7137bcfcb706471ab10613b9b61c5977 |
Test suite mostly follows the standard. I've started off with a couple of Result-checking tests, just to establish that we expect a Result back.
The floating-point tests are there, but commented out. Currently support for them is unimplemented. Still trying to decide if they should be implemented.
The approach I'm taking here is the Builder pattern, more-or-less. The Triangle struct does some boundary checking on the sides, but then return a Box containing one of the three concrete TriangleType implementations.
This API approach is, um, probably not the easiest. I know it's tricky because I had to ask Steve Klabnik if it was even possible
On the plus side, it introduces Boxes, which currently no problem uses. And it shows how you can do inheritance-like things in Rust.
That said, if everyone thinks it's a bad idea we can revisit.
Todo: