-
Notifications
You must be signed in to change notification settings - Fork 520
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 Bracket Push #178
Conversation
Oh, right, because we're making a And what would you say to the suggestion to instead make the interface Of course, internally I may use some structures that require me to track lifetimes, so don't worry that that's being lost. |
I will work on a solution soon to make sure everything is sane. I don't expect there will be anything groundbreaking in it so I'll simply wait until the exercise goes live to submit it, but I'll report whether there were any problems. |
pub struct Brackets; | ||
|
||
impl<'a> From<&'a str> for Brackets { | ||
fn from(i: &str) -> 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.
is s
better? or does the i
stand for "input"?
I don't actually care, but I ask in case it matters to someone
The current tests appear OK (implementation does fine on them) |
Re: Using the I think this approach works especially well with strong, static type languages. In Ruby I can get away with passing primitives around forever, thanks to its endless number of type-casting functions (both implicit and explicit). This gets harder in Rust because it won't do implicit type changes. Eventually you'll end up with an i8 and a u8 and want to do some math and run into a type error, for example. But if I get all primitives into types that I control, then I'm:
In short, I think it's good design. Which all seems like extreme overkill in an exercise like this, I agree. There is no additional behavior, there is no 'domain', there is only passing the tests. But if people are using these exercises to learn how to write Rust and the exercises encourage smelly Rust code, then won't most of the students go off and write smelly Rust code in production? |
Of the two choices of Since the motivation here is only to gain understanding for myself, there's no need to wait for this discussion before moving forward with this PR, since an acceptable resolution is already known.
I wonder if I jokingly said "For every design principle there is an equal and opposite design principle" whether it would be true. I did find that "It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures" and http://stackoverflow.com/questions/6016271/why-is-it-better-to-have-100-functions-operate-on-one-data-structure-than-10-fun attempted to explain why that would be the case. How would that apply to the matter at hand? I'm not sure. I think a situation I can imagine is: I have a collection of strings, and I want to first filter out the ones that contain valid brackets, and of the ones that do, I want to do something else with them, maybe From my reading, a good use of a custom type rather than a primitive is when not all possible values the primitive can take are valid for the custom type. That seems to make sense here: Not all strings are bracketed strings. Unfortunately TryFrom will not be stable for a while, otherwise I would find that interface very good:
Yes, I think this is often why I am interested in languages with static type systems, and taking advantage of their type systems. I like to avoid mistakes such as the adding of an i8 and a u8, or the mistake of adding two
I thought it would make them more likely, but you would be happy that you are running into the problems (happy that the compiler is catching the potential mistakes, rather than the code having weird bugs exposing themselves only at runtime)
Wholeheartedly agree. |
Thanks for the link to the SO question, that's a good perspective. I'm guessing this the sort of problem that Generics are trying to address? A single function can continue to work on your primitive types and all of your custom types. "Good Design" certainly isn't a checklist, it's a series of tradeoffs. No doubt. In a real-world application that checks brackets we might find that using |
b8506d8
to
e760aed
Compare
@@ -0,0 +1,62 @@ | |||
// The code below is a stub. Just enough to satisfy the compiler. |
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.
probably good to remove these from example
placement: If you would like to make a decision before being biased by mine, I put my final thoughts at the bottom. topics are: iteration over a string's characters, one of (stack OR recursive descent parser). maybe something to do with hash maps (but it's easy to imagine a solution that doesn't use one). I'll suggest either right between queen-attack and sublist. Can probably be easily convinced to place it anywhere between grade-school and wordy. Anywhere else will need more convincing. |
|
||
for &bracket in self.raw_brackets.iter() { | ||
if let Some(last_unclosed) = unclosed.pop() { | ||
for c in self.pairs.unmatched(last_unclosed, bracket) { |
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.
take a look at whether this can be expressed as an extend
which I think cn work because the docs say impl<T> Extend<T> for Vec<T>
.
the idea of letting the pairs
determine what's unmatched seems 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.
Huh! Extend! Learning things all the time. Good find.
I think @petertseng's instincts are right exercism#178 (comment) But I moved it before Queen Attack because I think having Queen Attack followed by Sublist is a good combo.
Everything seems to be coming together nicely. You mentioned you wanted to do something about |
Ok, I have been torturing this example code for far too long. It's fine. Really, it's just fine. I can save the code torture for when I submit solutions to exercism. I think everyone is ok with the API (at least no one has complained openly). I still want to:
|
I don't suppose you would find it too much to see if that use std::collections::HashMap;
fn main() {
let v = vec![(1, 2), (3, 4), (5, 6)];
let h1 = v.iter().cloned().collect::<HashMap<_, _>>();
let h2 = v.into_iter().collect::<HashMap<_, _>>();
println!("{:?}", h1);
println!("{:?}", h2);
} |
I'm pretty sure I've said all I have to say 👍 |
Full discussion of this change is at exercism#178 Tests ---- Tests follow the canonical json file. Example ____ The example is a bit of an experiment in Dependency Injection. I'm willing to admit that it might make this solution worse. But it doesn't affect the students at all, just the example. The idea here is that brackets and how they pair is a configuration detail, not a core part of the "are_balanced" algorithm. So I've changed Brackets so that it accepts an Option<Vec> of arbitrary brackets. Maybe you want to match "/" and "\", or "<" and ">". I dunno, I'm not you. Look at all the neat brackets you can use! https://en.wikipedia.org/wiki/Bracket But brackets has a default to fall back on should the dependency not be provided. Just using a bare Vec all over the place isn't great either (as far as Primitive Obsession goes, at least). So I've pulled that part out into its own named concept -- MatchingBrackets. MatchingBrackets should know if things are matched, right? So if can accept 2 unknown brackets and return the ones that are unmatched (which will always be 0 or 2) , then Brackets doesn't have to concern itself with what to push back into the array. It'll just shove in whatever `unmatched` returns. Currently the `closed_by` function lives in the wrong place. MatchingBrackets is about a collection of paired brackets, this function is concerned with a single set of paired brackets. The API for this should be `bracket.closed_by(other_bracket)` Instead of the nonsensical `brackets.closed_by(left_bracket, right_bracket)`. This will require the extraction of a new struct, though. And this example was already kind of overdone. Placing problems ---- I think @petertseng's instincts are right exercism#178 (comment) But I moved it before Queen Attack because I think having Queen Attack followed by Sublist is a good combo.
dee3ac7
to
dacaddf
Compare
I'm on the fence about using
&str
. If we don't provide the stub file, it will increase the difficulty of implementingfrom
.But if we include the stub file, then there's almost no extra work involved. Which also might reduce the amount students learn from the exercise.
Sometimes you gotta work with borrowed stuff, right? Writing a
from
implementation for a borrow seems like a useful thing to learn.So I think my current wish is to:
From<&'a str>
(From<&'static str>
would also work, I think)