Сохранение DRY в выражениях соответствия ржавчины

В качестве упрощенного самодостаточного примера предположим, что я анализирую входной файл, полный определений форм:

// shapes.txt
Circle: radius 1, color blue
Square: edge 5, color red
Triangle: edge 2 , color black
Triangle: edge 2 , color white

Я хочу разобрать их на такие структуры, как:

struct Circle {
    radius: i32,
    color: String
}

struct Square {
    edge: i32,
    color: String
}

struct Triangle {
    edge: i32,
    color: String
}

Я хотел бы разобрать их на набор векторов, зависящих от формы, например:

CircleDb: Vec<Circle>;
TriangleDb: Vec<Triangle>;
SquareDb: Vec<Square>;

... используя блок совпадений, например:

match inputFile.nextWord() {
    "Circle" => {
        Circle c = parseCircle(inputFile);
        CircleDb.push(c);
    },
    "Square" => {
        Square s = parseSquare(inputFile);
        SquareDb.push(s);
    },
    "Triangle" => {
        Triangle t = parseTriangle(inputFile);
        TriangleDb.push(t);
    },
}

А теперь представьте, что вместо 3-х форм у меня есть 10 или 15. Поэтому я не хочу повторять одну и ту же последовательность x=parseX(inputFile); XDb.push(x); в каждой ветви. Я лучше скажу что-то вроде:

let myMatcher = match inputFile.nextWord() {
    "Circle" => CircleMatcher,
    "Square" => SquareMatcher,
    "Triangle" => TriangleMatcher,
};
myMatcher.store(myMatcher.parse(inputFile));

Но я не могу придумать какого-либо последовательного способа определить Matcher структуру / тип / черту / что угодно, не нарушая ограничений средства проверки типов. Можно ли делать такие динамические вещи? Это хорошая идея? Я хотел бы получить представление о некоторых хороших шаблонах здесь.

Спасибо!


person Bosh    schedule 26.02.2015    source источник
comment
возможный дубликат Возврат и использование универсального типа с совпадением   -  person Shepmaster    schedule 26.02.2015
comment
Обратите внимание, что стиль Rust - это 4-пробельные отступы.   -  person Shepmaster    schedule 26.02.2015
comment
Это очень похож на вопрос о возврате и использовании универсального типа с соответствием, но решение не совсем то, о чем я спрашивал. В частности, я пытаюсь понять, можно ли избежать повторения логики синтаксического анализа и сохранения в каждой ветке.   -  person Bosh    schedule 27.02.2015
comment
Ваш последний пример, кажется, охватывает это - верните перечисление (например), которое охватывает все случаи, а затем инкапсулирует общую логику в методах этого перечисления.   -  person Shepmaster    schedule 27.02.2015


Ответы (2)


Хорошо, попробую ответить на ваш вопрос:

[возможно ли] избежать повторения логики "синтаксический анализ, затем сохранение" в каждой ветке

Ответ - да, но вам нужно будет абстрагироваться от уникальных частей и извлекать общие. Я немного изменил вашу проблему, чтобы иметь более простой пример. Здесь мы анализируем только одно целое число в зависимости от того, какой это тип фигуры.

Мы создаем новую структуру Foo, которая содержит концепцию «преобразовать u32 в некоторый тип, а затем сохранить их список». Для этого мы вводим две общие части - T, тип вещей, которые мы держим, и F, способ преобразования u32 в этот тип.

Чтобы обеспечить некоторую гибкость, я также создал и реализовал черту ShapeMatcher. Это позволяет нам получить ссылку на конкретный экземпляр Foo в общем виде - типажный объект. Если вам это не нужно, вы можете просто встроить черту обратно в Foo, а также встроить вызов match_it в ветви if. Это дополнительно описано в разделе Возврат и использование универсального типа с соответствием.

#[derive(Debug)]
struct Circle(u32);
#[derive(Debug)]
struct Square(u32);

struct Foo<T, F> {
    db: Vec<T>,
    matcher: F,
}

impl<T, F> Foo<T, F>
    where F: Fn(u32) -> T
{
    fn new(f: F) -> Foo<T, F> { Foo { db: Vec::new(), matcher: f } }
}

trait ShapeMatcher {
    fn match_it(&mut self, v: u32);
}

impl<T, F> ShapeMatcher for Foo<T, F>
    where F: Fn(u32) -> T
{
    fn match_it(&mut self, v: u32) {
        let x = (self.matcher)(v);
        self.db.push(x);
    }
}

fn main() {
    let mut circle_matcher = Foo::new(Circle);
    let mut square_matcher = Foo::new(Square);

    for &(shape, value) in &[("circle", 5),("circle", 42),("square", 9)] { 
        let matcher: &mut ShapeMatcher =
            if shape == "circle" { &mut circle_matcher }
            else                 { &mut square_matcher };

        matcher.match_it(value);
    }

    println!("{:?}", circle_matcher.db);
    println!("{:?}", square_matcher.db);
}
person Shepmaster    schedule 26.02.2015
comment
Спасибо! Объектный подход - это именно то, что я искал. Это прекрасно работает. - person Bosh; 27.02.2015
comment
@Bosh, это позор, потому что это означает, что этот вопрос дублируется. Объекты-черты являются третьим примером в связанном вопросе - person Shepmaster; 27.02.2015
comment
Я не совсем согласен - объекты признаков были ключевыми для обоих вопросов, но мой вопрос касался того, как абстрагировать логику в эти объекты признаков, и stackoverflow.com/questions/28589764/ этого не добился. Конечно, если бы я был достаточно знаком с Rust, я мог бы опираться на другой ответ, но, конечно, это не так. Как новичку в Rust, мне сложно обобщать и применять эти методы в разных контекстах. Итак, еще раз: спасибо :-) - person Bosh; 27.02.2015

Еще один вариант, позволяющий избежать шаблонного кода, - это какой-то вид встроенного предметно-ориентированного языка с поддержкой макросов (eDSL). Это не всегда лучшая идея (особенно в Rust), но иногда этот метод более выразителен для таких задач, как ваша. Например, рассмотрим синтаксис:

    shapes_parse! { 
        inspecting line; { 
            Circle into circle_db,
            Square into square_db,
            Triangle into triangle_db
        }
    }

который раскрывается в следующем коде:

    match line[0] {
        "Circle" => { circle_db.push(Circle::parse(&line[1..])); },
        "Square" => { square_db.push(Square::parse(&line[1..])); },
        "Triangle" => { triangle_db.push(Triangle::parse(&line[1..])); },
        other => panic!("Unexpected type: {}", other),
    }

используя этот макрос:

macro_rules! shapes_parse {
    ( inspecting $line:expr; { $($name:ident into $db:expr),* } ) => {
        match $line[0] {
            $( stringify!($name) => { $db.push($name::parse(&$line[1..])); } )+
            other => panic!("Unexpected shape: {}", other),
        }
    };
}

рабочий пример на манеже

person swizard    schedule 27.02.2015