Key Takeaways
- Undertaking Amber has introduced quite a lot of new options to Java in recent times. Whereas every of those options are self-contained, they’re additionally designed to work collectively. Particularly, data, sealed lessons, and sample matching work collectively to allow simpler data-oriented programming in Java.
- OOP encourages us to mannequin advanced entities and processes utilizing objects, which mix state and habits. OOP is at its finest when it’s defining and defending boundaries.
- Java’s robust static typing and class-based modeling can nonetheless be tremendously helpful for smaller applications, simply in numerous methods.
- Knowledge-oriented programming encourages us to mannequin information as (immutable) information, and preserve the code that embodies the enterprise logic of how we act on that information individually. Information, sealed lessons, and sample matching, make that simpler.
- After we’re modeling advanced entities, OO methods have loads to supply us. However after we’re modeling easy providers that course of plain, ad-hoc information, the methods of data-oriented programming might supply us a straighter path.
- The methods of OOP and data-oriented programming should not at odds; they’re completely different instruments for various granularities and conditions. We are able to freely combine and match them as we see match.
Project Amber has introduced quite a lot of new options to Java in recent times — local variable type inference, text blocks, records, sealed classes, pattern matching, and extra. Whereas every of those options are self-contained, they’re additionally designed to work collectively. Particularly, data, sealed lessons, and sample matching work collectively to allow simpler data-oriented programming in Java. On this article, we’ll cowl what is supposed by this time period and the way it may have an effect on how we program in Java.
Object-oriented programming
The aim of any programming paradigm is to handle complexity. However complexity is available in many varieties, and never all paradigms deal with all types of complexity equally effectively. Most programming paradigms have a one-sentence slogan of the shape “Every thing is a …”; for OOP, that is clearly “all the things is an object.” Practical programming says “all the things is a operate”; actor-based programs say “all the things is an actor”, and so forth. (After all, these are all overstatements for impact.)
OOP encourages us to mannequin advanced entities and processes utilizing objects, which mix state and habits. OOP encourages encapsulation (object habits mediates entry to object state) and polymorphism (a number of sorts of entities might be interacted with utilizing a standard interface or vocabulary), although the mechanisms for conducting these targets fluctuate throughout OO languages. When modeling the world with objects, we’re inspired to assume by way of is-a (a financial savings account is-a checking account) and has-a (a financial savings account has-a proprietor and account quantity) relationships.
Whereas some builders benefit from loudly declaring object-oriented programming to be a failed experiment, the reality is extra refined; like all instruments, it’s well-suited to some issues and fewer well-suited to others. OOP completed badly might be terrible, and lots of people have been uncovered to OOP ideas taken to ridiculous extremes. (Rants like the kingdom of nouns could also be amusing and therapeutic, however they’re not likely railing towards OOP, as a lot as a cartoon exaggeration of OOP.) But when we perceive what OOP is healthier or worse at, we are able to use it the place it gives extra worth and use one thing else the place it gives much less.
OOP is at its finest when it’s defining and defending boundaries — upkeep boundaries, versioning boundaries, encapsulation boundaries, compilation boundaries, compatibility boundaries, safety boundaries, and so forth.
Independently maintained libraries are constructed, maintained, and developed individually from the purposes that rely upon them (and from one another), and if we wish to have the ability to freely transfer from one model of the library to the following, we have to be certain that boundaries between libraries and their purchasers are clear, well-defined, and deliberate. Platform libraries might have privileged entry to the underlying working system and {hardware}, which should be rigorously managed; we want a powerful boundary between the platform libraries and the appliance to protect system integrity. OO languages present us with instruments for exactly defining, navigating, and defending these boundaries.
Dividing a big program into smaller components with clear boundaries helps us handle complexity as a result of it permits modular reasoning — the flexibility to investigate one a part of this system at a time, however nonetheless cause about the entire. In a monolithic program, inserting smart inside boundaries helped us construct greater purposes that spanned a number of groups. It’s no accident that Java thrived within the period of monoliths.
Since then, applications have shrunk; reasonably than constructing monoliths, we compose bigger purposes out of many smaller providers. Inside a small service, there’s much less want for inside boundaries; small enough providers might be maintained by a single workforce (or perhaps a single developer.) Equally, inside such smaller providers, we now have much less want for modeling long-running stateful processes.
Knowledge-oriented programming
Java’s robust static typing and class-based modeling can nonetheless be tremendously helpful for smaller applications, simply in numerous methods. The place OOP encourages us to make use of lessons to mannequin enterprise entities and processes, smaller codebases with fewer inside boundaries will typically get extra mileage out of utilizing lessons to mannequin information. Our providers eat requests that come from the skin world, corresponding to by way of HTTP requests with untyped JSON/XML/YAML payloads. However solely probably the most trivial of providers would wish to work instantly with information on this kind; we would prefer to signify numbers as int
or lengthy
reasonably than as strings of digits, dates as lessons like LocalDateTime
, and lists as collections reasonably than lengthy comma-delimited strings. (And we wish to validate that information on the boundary, earlier than we act on it.)
Knowledge-oriented programming encourages us to mannequin information as (immutable) information, and preserve the code that embodies the enterprise logic of how we act on that information individually. As this pattern in the direction of smaller applications has progressed, Java has acquired new instruments to make it simpler to mannequin information as information (records), to instantly mannequin alternate options (sealed classes), and to flexibly destructure polymorphic information (pattern matching) patterns.
Knowledge-oriented programming encourages us to mannequin information as information. Information, sealed lessons, and sample matching, work collectively to make that simpler.
Programming with information as information doesn’t suggest giving up static typing. One might do data-oriented programming with solely untyped maps and lists (one typically does in languages like Javascript), however static typing nonetheless has loads to supply by way of security, readability, and maintainability, even after we are solely modeling plain information. (Undisciplined data-oriented code is usually known as “stringly typed”, as a result of it makes use of strings to mannequin issues that should not be modeled as strings, corresponding to numbers, dates, and lists.)
Knowledge oriented programming in Java
Information, sealed lessons, and sample matching are designed to work collectively to help data-oriented programming. Information enable us to easily mannequin information utilizing lessons; sealed lessons allow us to mannequin selections; and sample matching supplies us with a straightforward and type-safe approach of appearing on polymorphic information. Help for sample matching has are available in a number of increments; the primary added solely type-test patterns and solely supported them in instanceof
; the following supported type-test patterns in change
as effectively; and most just lately, deconstruction patterns for records have been added in Java 19. The examples on this article will make use of all of those options.
Whereas data are syntactically concise, their major power is that they allow us to cleanly and easily mannequin aggregates. Simply as with all information modeling, there are artistic selections to make, and a few modelings are higher than others. Utilizing the mixture of data and sealed lessons additionally makes it simpler to make unlawful states unrepresentable, additional bettering security and maintainability.
Instance — command-line choices
As a primary instance, take into account how we’d mannequin invocation choices in a command line program. Some choices take arguments; some don’t. Some arguments are arbitrary strings, whereas others are extra structured, corresponding to numbers or dates. Processing command line choices ought to reject dangerous choices and malformed arguments early within the execution of this system. A fast-and-dirty method may be to loop via the command line arguments and for every recognized choice we encounter, squirrel away the presence or absence of the choice, and probably the choice’s parameter, in variables. That is easy, however now our program relies on a set of stringly-typed, successfully world variables. If our program is tiny, this may be OK, however it would not scale very effectively. Not solely is that this prone to impede maintainability as this system grows, however it makes our program much less testable — we are able to solely check this system as a complete via its command line.
A barely much less quick-and-dirty method may be to create a single class representing a command line choice, and parse the command line into a listing of choice objects. If we had a cat
-like program that copies strains from a number of recordsdata to a different, can trim recordsdata to a sure line rely, and might optionally embody line numbers, we’d mannequin these choices utilizing an enum
and an Choice
class:
enum MyOptions INPUT_FILE, OUTPUT_FILE, MAX_LINES, PRINT_LINE_NUMBERS
file OptionValue(MyOptions choice, String optionValue)
static Checklist<OptionValue> parseOptions(String[] args) ...
That is an enchancment over the earlier method; not less than now there’s a clear separation between parsing the command line choices and consuming them, which implies we are able to check our enterprise logic individually from the command-line shell by feeding it lists of choices. However it’s nonetheless not excellent. Some choices haven’t any parameter, however we will not see that from wanting on the enum of choices, and we nonetheless mannequin them with an OptionValue
object that has an optionValue
area. And even for choices that do have parameters, they’re at all times stringly typed.
The higher approach to do that is to mannequin every choice instantly. Traditionally, this might need been prohibitively verbose, however happily that is now not the case. We are able to used a sealed class to signify an Choice
, and have a file for every sort of choice:
sealed interface Choice
file InputFile(Path path) implements Choice
file OutputFile(Path path) implements Choice
file MaxLines(int maxLines) implements Choice
file PrintLineNumbers() implements Choice
The Choice
subclasses are pure information. The choice values have good clear names and kinds; choices which have parameters signify them with the suitable sort; choices with out parameters do not need ineffective parameter variables that may be misinterpreted. Additional, it’s simple to course of the choices with a sample matching change (normally one line of code per sort of choice.) And since Choice
is sealed, the compiler can type-check {that a} change handles all the choice sorts. (If we add extra choice sorts later, the compiler will remind us which switches have to be prolonged.)
We have in all probability all written code like that outlined within the first two variations, regardless that we all know higher. With out the flexibility to cleanly and concisely mannequin the information, doing it “proper” is usually an excessive amount of work (or an excessive amount of code.)
What we have completed right here is take messy, untyped information from throughout the invocation boundary (command line arguments) and remodeled it into information that’s strongly typed, validated, simply acted upon (via sample matching), and makes many unlawful states (corresponding to specifying --input-file
however not offering a sound path) unrepresentable. The remainder of this system can simply use it with confidence.
Algebraic information sorts
This mix of data and sealed sorts is an instance of what are known as algebraic information sorts (ADTs). Information are a type of “product sorts”, so-called as a result of their state house is the cartesian product of that of their parts. Sealed lessons are a type of “sum sorts”, so-called as a result of the set of attainable values is the sum (union) of the worth units of the alternate options. This straightforward mixture of mechanisms — aggregation and selection — is deceptively highly effective, and reveals up in lots of programming languages. (Our instance right here was restricted to at least one stage of hierarchy, however this needn’t be the case typically; one of many permitted subtypes of a sealed interface could possibly be one other sealed interface, permitting modeling of advanced constructions.)
In Java, algebraic information sorts might be modeled exactly as sealed hierarchies whose leaves are data. Java’s interpretation of algebraic information sorts have quite a lot of fascinating properties. They’re nominal — the categories and parts have human-readable names. They’re immutable, which makes them less complicated and safer and might be freely shared with out fear of interference. They’re simply testable, as a result of they comprise nothing however their information (probably with habits derived purely from the information). They will simply be serialized to disk or throughout the wire. And they’re expressive — they will mannequin a broad vary of information domains.
Utility: advanced return sorts
One of many easiest however most regularly used purposes of algebraic information sorts is advanced return sorts. Since a way can solely return a single worth, it’s typically tempting to overload the illustration of the return worth in questionable or advanced methods, corresponding to utilizing null
to imply “not discovered”, encoding a number of values right into a string, or utilizing a very summary sort (arrays, Checklist
or Map
) to cram all of the completely different sorts of data a way might return right into a single service object. Algebraic information sorts make it really easy to do the precise factor, that these approaches turn into much less tempting.
In Sealed Classes, we gave an instance of how this system that could possibly be used to summary over each success and failure situations with out utilizing exceptions:
sealed interface AsyncReturn<V>
file Success<V>(V consequence) implements AsyncReturn<V>
file Failure<V>(Throwable trigger) implements AsyncReturn<V>
file Timeout<V>() implements AsyncReturn<V>
file Interrupted<V>() implements AsyncReturn<V>
The benefit of this method is that the shopper can deal with success and failure uniformly by sample matching over the consequence, reasonably than having to deal with success by way of the return worth and the varied failure modes by way of separate catch
blocks:
AsyncResult<V> r = future.get();
change (r)
case Success<V>(var consequence): ...
case Failure<V>(Throwable trigger): ...
case Timeout<V>(): ...
case Interrupted<V>(): ...
One other advantage of sealed lessons is that if you happen to change over them and not using a default
, the compiler will remind you if you happen to’ve forgotten a case. (Checked exceptions do that too, however in a extra intrusive method.)
As one other instance, think about a service that appears up entities (customers, paperwork, teams, and so forth) by identify, and which distinguishes between “no match discovered”, “precise match discovered”, and “no precise match, however there have been shut matches.” We are able to all think about methods to cram this right into a single Checklist
or array, and whereas this may occasionally make the search API simple to write, it makes it more durable to know, use, or check. Algebraic information sorts make each side of this equation simple. We are able to craft a concise API that claims precisely what we imply:
sealed interface MatchResult<T>
file NoMatch<T>() implements MatchResult<T>
file ExactMatch<T>(T entity) implements MatchResult<T>
file FuzzyMatches<T>(Assortment<FuzzyMatch<T>> entities)
implements MatchResult<T>
file FuzzyMatch<T>(T entity, int distance)
MatchResult<Person> findUser(String userName) ...
If we encountered this return hierarchy whereas shopping the code or the Javadoc, it’s instantly apparent what this methodology may return, and the right way to deal with its consequence:
Web page userSearch(String person) {
return change (findUser(person))
case NoMatch() -> noMatchPage(person);
case ExactMatch(var u) -> userPage(u);
case FuzzyMatches(var ms) -> disambiguationPage(ms.stream()
.sorted(FuzzyMatch::distance))
.restrict(MAX_MATCHES)
.toList());
Whereas such a transparent encoding of the return worth is nice for the readability of the API and for its ease of use, such encodings are additionally typically simpler to write as effectively, as a result of the code nearly writes itself from the necessities. However, attempting to provide you with (and doc) “intelligent” encodings that cram advanced outcomes into summary carriers like arrays or maps takes extra work.
Utility: Advert-hoc information constructions
Algebraic information sorts are additionally helpful for modeling ad-hoc variations of normal function information constructions. The favored class Non-compulsory
could possibly be modeled as an algebraic information sort:
sealed interface Decide<T>
file Some<T>(T worth) implements Decide<T>
file None<T>() implements Decide<T>
(That is really how Non-compulsory
is outlined in most practical languages.) Widespread operations on Decide
might be applied with sample matching:
static<T, U> Decide<U> map(Decide<T> decide, Perform<T, U> mapper)
return change (decide)
case Some<T>(var v) -> new Some<>(mapper.apply(v));
case None<T>() -> new None<>();
Equally, a binary tree might be applied as:
sealed interface Tree<T>
file Nil<T>() implements Tree<T>
file Node<T>(Tree<T> left, T val, Tree<T> proper) implements Tree<T>
and we are able to implement the standard operations with sample matching:
static<T> boolean comprises(Tree<T> tree, T goal)
return change (tree) ;
static<T> void inorder(Tree<T> t, Shopper<T> c)
change (tree)
case Nil(): break;
case Node(var left, var val, var proper):
inorder(left, c);
c.settle for(val);
inorder(proper, c);
;
It could appear odd to see this habits written as static strategies, when frequent behaviors like traversal ought to “clearly” be applied as summary strategies on the bottom interface. And positively, some strategies might effectively make sense to place into the interface. However the mixture of data, sealed lessons, and sample matching gives us alternate options that we did not have earlier than; we might implement them the quaint approach (with an summary methodology within the base class and concrete strategies in every subclass); as default strategies within the summary class applied in a single place with sample matching; as static strategies; or (when recursion is just not wanted), as ad-hoc traversals inline on the level of use.
As a result of the information service is purpose-built for the state of affairs, we get to decide on whether or not we wish the habits to journey with the information or not. This method is just not at odds with object orientation; it’s a helpful addition to our toolbox that can be utilized alongside OO, because the state of affairs calls for.
Instance: JSON
For those who look intently sufficient on the JSON spec, you may see {that a} JSON worth can also be an ADT:
sealed interface JsonValue
file JsonString(String s) implements JsonValue
file JsonNumber(double d) implements JsonValue
file JsonNull() implements JsonValue
file JsonBoolean(boolean b) implements JsonValue
file JsonArray(Checklist<JsonValue> values) implements JsonValue
file JsonObject(Map<String, JsonValue> pairs) implements JsonValue
When offered as such, the code to extract the related bits of data from a blob of JSON is fairly simple; if we wish to match the JSON blob "identify":"John", "age":30, "metropolis":"New York"
with sample matching, that is:
if (j instanceof JsonObject(var pairs)
&& pairs.get("identify") instanceof JsonString(String identify)
&& pairs.get("age") instanceof JsonNumber(double age)
&& pairs.get("metropolis") instanceof JsonString(String metropolis))
// use identify, age, metropolis
After we mannequin information as information, each creating aggregates and taking them aside to extract their contents (or repack them into one other kind) is simple, and since sample matching fails gracefully when one thing would not match, the code to take aside this JSON blob is comparatively freed from advanced management circulate for implementing structural constraints. (Whereas we may be inclined to make use of a extra industrial-strength JSON library than this toy instance, we might really implement the toy with just a few dozen further strains of parsing code which follows the lexical guidelines outlined within the JSON spec and turns them right into a JsonValue
.)
Extra advanced domains
The domains we have checked out to date have both been “throwaways” (return values used throughout a name boundary) or modeling normal domains like lists and timber. However the identical method can also be helpful for extra advanced application-specific domains. If we needed to mannequin an arithmetic expression, we might achieve this with:
sealed interface Node
sealed interface BinaryNode extends Node
Node left();
Node proper();
file AddNode(Node left, Node proper) implements BinaryNode
file MulNode(Node left, Node proper) implements BinaryNode
file ExpNode(Node left, int exp) implements Node
file NegNode(Node node) implements Node
file ConstNode(double val) implements Node
file VarNode(String identify) implements Node
Having the intermediate sealed interface BinaryNode
which abstracts over addition and multiplication offers us the selection when matching over a Node
; we might deal with each addition and multiplication collectively by matching on BinaryNode
, or deal with them individually, because the state of affairs requires. The language will nonetheless be sure we coated all of the circumstances.
Writing an evaluator for these expressions is trivial. Since we now have variables in our expressions, we’ll want a retailer for these, which we move into the evaluator:
double eval(Node n, Perform<String, Double> vars)
return change (n)
case AddNode(var left, var proper) -> eval(left, vars) + eval(proper, vars);
case MulNode(var left, var proper) -> eval(left, vars) * eval(proper, vars);
case ExpNode(var node, int exp) -> Math.exp(eval(node, vars), exp);
case NegNode(var node) -> -eval(node, vars);
case ConstNode(double val) -> val;
case VarNode(String identify) -> vars.apply(identify);
The data which outline the terminal nodes have cheap toString
implementations, however the output might be extra verbose than we would like. We are able to simply write a formatter to supply output that appears extra like a mathematical expression:
String format(Node n)
return change (n)
case AddNode(var left, var proper) -> String.format("("%s + %s)",
format(left), format(proper));
case MulNode(var left, var proper) -> String.format("("%s * %s)",
format(left), format(proper));
case ExpNode(var node, int exp) -> String.format("%s^%d", format(node), exp);
case NegNode(var node) -> String.format("-%s", format(node));
case ConstNode(double val) -> Double.toString(val);
case VarNode(String identify) -> identify;
As earlier than, we might specific these as static strategies, or implement them within the base class as occasion strategies however with a single implementation, or implement them as atypical occasion strategies — we’re free to decide on which feels most readable for the area.
Having outlined our area abstractly, we are able to simply add different operations on it as effectively. We are able to symbolically differentiate with respect to a single variable simply:
Node diff(Node n, String v)
return change (n)
case AddNode(var left, var proper)
-> new AddNode(diff(left, v), diff(proper, v));
case MulNode(var left, var proper)
-> new AddNode(new MulNode(left, diff(proper, v)),
new MulNode(diff(left, v), proper)));
case ExpNode(var node, int exp)
-> new MulNode(new ConstNode(exp),
new MulNode(new ExpNode(node, exp-1),
diff(node, v)));
case NegNode(var node) -> new NegNode(diff(node, var));
case ConstNode(double val) -> new ConstNode(0);
case VarNode(String identify) -> identify.equals(v) ? new ConstNode(1) : new ConstNode(0);
Earlier than we had data and sample matching, the usual method to writing code like this was the customer sample. Sample matching is clearly extra concise than guests, however it’s also extra versatile and highly effective. Guests require the area to be constructed for visitation, and imposes strict constraints; sample matching helps rather more ad-hoc polymorphism. Crucially, sample matching composes higher; we are able to use nested patterns to specific advanced situations that may be a lot messier to specific utilizing guests. For instance, the above code will yield unnecessarily messy timber when, say, we now have a multiplication node the place one subnode is a continuing. We are able to use nested patterns to deal with these particular circumstances extra eagerly:
Node diff(Node n, String v)
return change (n)
case AddNode(var left, var proper)
-> new AddNode(diff(left, v), diff(proper, v));
// particular circumstances of ok*node, or node*ok
case MulNode(var left, ConstNode(double val) ok)
-> new MulNode(ok, diff(left, v));
case MulNode(ConstNode(double val) ok, var proper)
-> new MulNode(ok, diff(proper, v));
case MulNode(var left, var proper)
-> new AddNode(new MulNode(left, diff(proper, v)),
new MulNode(diff(left, v), proper)));
case ExpNode(var node, int exp)
-> new MulNode(new ConstNode(exp),
new MulNode(new ExpNode(node, exp-1),
diff(node, v)));
case NegNode(var node) -> new NegNode(diff(node, var));
case ConstNode(double val) -> new ConstNode(0);
case VarNode(String identify) -> identify.equals(v) ? new ConstNode(1) : new ConstNode(0);
Doing this with guests — particularly at a number of ranges of nesting — can rapidly turn into fairly messy and error-prone.
It isn’t both/or
Lots of the concepts outlined right here might look, at first, to be considerably “un-Java-like”, as a result of most of us have been taught to begin by modeling entities and processes as objects. However in actuality, our applications typically work with comparatively easy information, which regularly comes from the “outdoors world” the place we will not rely on it becoming cleanly into the Java sort system. (In our JSON instance, we modeled numbers as double
, however in truth the JSON specification is silent on the vary of numeric values; code on the boundary of a system goes to should decide of whether or not to truncate or reject values that do not match into the native illustration.)
After we’re modeling advanced entities, or writing wealthy libraries corresponding to java.util.stream
, OO methods have loads to supply us. However after we’re constructing easy providers that course of plain, ad-hoc information, the methods of data-oriented programming might supply us a straighter path. Equally, when exchanging advanced outcomes throughout an API boundary (corresponding to our match consequence instance), it’s typically less complicated and clearer to outline an ad-hoc information schema utilizing ADTs, than to complect outcomes and habits in a stateful object (because the Java Matcher
API does.)
The methods of OOP and data-oriented programming should not at odds; they’re completely different instruments for various granularities and conditions. We are able to freely combine and match them as we see match.
Comply with the information
Whether or not modeling a easy return worth, or a extra advanced area corresponding to JSON or our expression timber, there are some easy ideas that normally lead us to easy, dependable data-oriented code.
-
Mannequin the information, the entire information, and nothing however the information. Information ought to mannequin information. Make every file mannequin one factor, make it clear what every file fashions, and select clear names for its parts. The place there are selections to be modeled, corresponding to “a tax return is filed both by the taxpayer, or by a authorized consultant”, mannequin these as sealed lessons, and mannequin every different with a file. Conduct in file lessons must be restricted to implementing derived portions from the information itself, corresponding to formatting.
-
Knowledge is immutable. An object that has a mutable
int
area doesn’t mannequin an integer; it fashions a time-varying relationship between a particular object identification and an integer. If we wish to mannequin information, we should always not have to fret about our information altering out from beneath us. Information give us some assist right here, as they’re shallowly immutable, however it nonetheless requires some self-discipline to keep away from letting mutability inject itself into our information fashions. -
Validate on the boundary. Earlier than injecting information into our system, we should always be certain that it’s legitimate. This may be completed within the file constructor (if the validation applies universally to all cases), or by the code on the boundary that has obtained the information from one other supply.
-
Make unlawful states unrepresentable. Information and sealed sorts make it simple to mannequin our domains in such a approach that misguided states merely can’t be represented. That is significantly better than having to test for validity on a regular basis! Simply as immutability eliminates many frequent sources of errors in applications, so does avoiding modeling methods that enable us to mannequin invalid information.
A hidden advantage of this method is testability. Not solely is it simple to check code when its inputs and outputs are easy, well-defined information, however it opens the door to simpler generative testing, which is usually far more practical at discovering bugs than hand-crafting particular person check circumstances.
The mix of data, sealed sorts, and sample matching makes it simple to observe these ideas, yielding extra concise, readable, and extra dependable applications. Whereas programming with information as information could also be a bit of unfamiliar given Java’s OO underpinnings, these methods are effectively price including to our toolbox.