Verschachtelte monadische Operationen erzeugen unlesbaren CodeC++

Programme in C++. Entwicklerforum
Guest
 Verschachtelte monadische Operationen erzeugen unlesbaren Code

Post by Guest »

Ich brauche Hilfe beim Schreiben von lesbarem Code zum Erstellen der monadischen Rückrufe von C++23. Ich stelle fest, dass der Code, den ich erzeuge, zwar tendenziell korrekter ist, aber Szenarios, in denen ich die Ergebnisse zweier Berechnungen zusammenführen muss, Code erzeugen, der deutlich schlechter lesbar ist als die Standardmethode.
Nehmen Sie ein Beispiel mit optionalen Funktionen mit der Einschränkung, dass foo() vor bar() aufgerufen werden muss: Nehmen wir als motivierendes Beispiel an, wir schreiben einen Parser und beide foo und bar verbrauchen etwas Token aus unserer Liste der zu analysierenden Token.

Code: Select all

std::optional foo();
std::optional bar();
Und multiplizieren Sie ihre Ergebnisse, wenn sie existieren, oder verbreiten Sie ein Nullopt andernfalls
„Die übliche Vorgehensweise“

Code: Select all

std::optional baz() {
std::optional maybe_foo = foo();
if(!maybe_foo.has_value()) {
return std::nullopt;
}
std::optional maybe_bar = bar();
if(!maybe_bar.has_value()) {
return std::nullopt;
}
return maybe_foo.value() * maybe_bar.value();
}
Monadischer Ansatz:

Code: Select all

std::optional baz() {
return foo().and_then([](int foo_res) {
return bar().and_then([foo_res](int bar_res) {
return foo_res * bar_res;
});
});
}
Und diese Verschachtelung macht mir wirklich Sorgen. Bei komplizierteren Berechnungen stelle ich fest, dass es noch schlimmer wird, wenn diese wachsende Logikpyramide aus meinen Funktionen herausschießt, da wir niemals in der Lage sind, unsere Logik kurzzuschließen.
Was mache ich falsch?
Als anschaulicheres Beispiel ist unten eine Funktion eines Parsers aufgeführt, die ich schreibe und die ich für besonders unlesbar halte. Die Funktionalität der Funktion ist weniger wichtig als die oben beschriebene Callback-Pyramide...

Code: Select all

template 
Parser::maybe_expression Parser::assignment() {
// Given an arbitrary expression, return the VariableExpression contained within
// if one exists, otherwise return a nullopt
auto try_extract_variable = [](grammar::Expression expr)
-> std::optional {
return try_get(std::move(expr))
.and_then([](grammar::PrimaryExpression primary_expr) -> std::optional {
return try_get(std::move(primary_expr.data));
});
};
return equality()
.and_then([&](std::unique_ptr expr) {
// If the top token after parsing Equality() is an =, we either return an
// assignment expression or an error. Otherwise, we directly return the Equality() expression
return consume()
.transform([&](const token::Equal &equal) {
// We are parsing an assignment expression, and so we would like to extract the
// Variable that we are to assign, otherwise return an error.
return try_extract_variable(std::move(*expr))
.transform([&](const grammar::VariableExpression &variable) -> maybe_expression {
return expression()
.map([&](std::unique_ptr assign_to) {
return std::make_unique(grammar::AssignmentExpression{
variable, std::move(assign_to), variable.line_number
});
});
})
.value_or(tl::unexpected{Error{equal.line_number,
ErrorType::kBadAssign,
fmt::format("Incomplete assignment expression")
}});
})
.or_else([&] -> std::optional {
return std::move(expr);
})
.value();
});
}

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post