Class APIs
On this page
When working with schemas, you have a choice beyond the Schema.Struct constructor.
You can leverage the power of classes through the Schema.Class
utility, which comes with its own set of advantages tailored to common use cases:
Classes offer several features that simplify the schema creation process:
- All-in-One Definition: With classes, you can define both a schema and an opaque type simultaneously.
- Shared Functionality: You can incorporate shared functionality using class methods or getters.
- Value Hashing and Equality: Utilize the built-in capability for checking value equality and applying hashing (thanks to
Class
implementing Data.Class).
Definition
To define a Class
in @effect/schema
, you need to provide:
- The type of the class being created.
- A unique identifier for the class.
- The desired fields.
Example
ts
import {Schema } from "@effect/schema"// Define your schema by providing the type, a unique identifier and the desired fieldsclassPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {}
ts
import {Schema } from "@effect/schema"// Define your schema by providing the type, a unique identifier and the desired fieldsclassPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {}
In this setup, Person
is a class where id
is a number and name
is a non-empty string.
The constructor for the class creates instances with these specified properties.
ts
console .log (newPerson ({id : 1,name : "John" }))/*Output:Person { id: 1, name: 'John' }*/// orconsole .log (Person .make ({id : 1,name : "John" }))/*Output:Person { id: 1, name: 'John' }*/
ts
console .log (newPerson ({id : 1,name : "John" }))/*Output:Person { id: 1, name: 'John' }*/// orconsole .log (Person .make ({id : 1,name : "John" }))/*Output:Person { id: 1, name: 'John' }*/
Classes Without Arguments
If your schema does not require any fields, you can define a class with an empty object:
ts
import {Schema } from "@effect/schema"classNoArgs extendsSchema .Class <NoArgs >("NoArgs")({}) {}constnoargs1 = newNoArgs ()// same asconstnoargs2 = newNoArgs ({})
ts
import {Schema } from "@effect/schema"classNoArgs extendsSchema .Class <NoArgs >("NoArgs")({}) {}constnoargs1 = newNoArgs ()// same asconstnoargs2 = newNoArgs ({})
Class Constructor as a Validator
When you define a class using Schema.Class
, the constructor automatically checks that the provided properties adhere to the schema's rules. Here's how you can define and instantiate a Person
class:
ts
import {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {}// okconstjohn = newPerson ({id : 1,name : "John" })john .id john .name
ts
import {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {}// okconstjohn = newPerson ({id : 1,name : "John" })john .id john .name
This ensures that each property of the Person
instance, like id
and name
, meets the conditions specified in the schema, such as id
being a number and name
being a non-empty string.
If an instance is created with invalid properties, the constructor throws an error detailing what went wrong:
ts
newPerson ({id : 1,name : "" }) // Attempting to instantiate with an invalid name/*throws:ParseError: Person (Constructor)└─ ["name"]└─ a non empty string└─ Predicate refinement failure└─ Expected a non empty string, actual ""*/
ts
newPerson ({id : 1,name : "" }) // Attempting to instantiate with an invalid name/*throws:ParseError: Person (Constructor)└─ ["name"]└─ a non empty string└─ Predicate refinement failure└─ Expected a non empty string, actual ""*/
This error message clearly states that the name
field failed the non-empty string predicate, providing precise feedback on why the validation failed.
There are scenarios where you might want to bypass validation during instantiation. Although not typically recommended, @effect/schema
allows for this flexibility:
ts
// Bypasses validation, thus avoiding errorsconstjohn = newPerson ({id : 1,name : "" }, true)// or more explicitlynewPerson ({id : 1,name : "" }, {disableValidation : true })
ts
// Bypasses validation, thus avoiding errorsconstjohn = newPerson ({id : 1,name : "" }, true)// or more explicitlynewPerson ({id : 1,name : "" }, {disableValidation : true })
Hashing and Equality
Thanks to the implementation of Data.Class, instances of your classes automatically support the Equal trait, which allows for easy comparison:
ts
import {Schema } from "@effect/schema"import {Equal } from "effect"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {}constjohn1 = newPerson ({id : 1,name : "John" })constjohn2 = newPerson ({id : 1,name : "John" })console .log (Equal .equals (john1 ,john2 )) // Output: true
ts
import {Schema } from "@effect/schema"import {Equal } from "effect"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {}constjohn1 = newPerson ({id : 1,name : "John" })constjohn2 = newPerson ({id : 1,name : "John" })console .log (Equal .equals (john1 ,john2 )) // Output: true
However, be aware that the Equal
trait checks for equality only at the first level. If, for instance, a field is an array, the returned instances will not be considered equal:
ts
import {Schema } from "@effect/schema"import {Equal } from "effect"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString ,hobbies :Schema .Array (Schema .String )}) {}constjohn1 = newPerson ({id : 1,name : "John",hobbies : ["reading", "coding"]})constjohn2 = newPerson ({id : 1,name : "John",hobbies : ["reading", "coding"]})console .log (Equal .equals (john1 ,john2 )) // Output: false
ts
import {Schema } from "@effect/schema"import {Equal } from "effect"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString ,hobbies :Schema .Array (Schema .String )}) {}constjohn1 = newPerson ({id : 1,name : "John",hobbies : ["reading", "coding"]})constjohn2 = newPerson ({id : 1,name : "John",hobbies : ["reading", "coding"]})console .log (Equal .equals (john1 ,john2 )) // Output: false
To ensure deep equality for arrays, use Schema.Data
combined with Data.array
:
ts
import {Schema } from "@effect/schema"import {Data ,Equal } from "effect"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString ,hobbies :Schema .Data (Schema .Array (Schema .String ))}) {}constjohn1 = newPerson ({id : 1,name : "John",hobbies :Data .array (["reading", "coding"])})constjohn2 = newPerson ({id : 1,name : "John",hobbies :Data .array (["reading", "coding"])})console .log (Equal .equals (john1 ,john2 )) // Output: true
ts
import {Schema } from "@effect/schema"import {Data ,Equal } from "effect"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString ,hobbies :Schema .Data (Schema .Array (Schema .String ))}) {}constjohn1 = newPerson ({id : 1,name : "John",hobbies :Data .array (["reading", "coding"])})constjohn2 = newPerson ({id : 1,name : "John",hobbies :Data .array (["reading", "coding"])})console .log (Equal .equals (john1 ,john2 )) // Output: true
Custom Getters and Methods
You have the flexibility to enhance schema classes with custom getters and methods.
Example
ts
import {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {// Custom getter to return the name in uppercasegetupperName () {return this.name .toUpperCase ()}}constjohn = newPerson ({id : 1,name : "John" })console .log (john .upperName ) // Output: "JOHN"
ts
import {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {// Custom getter to return the name in uppercasegetupperName () {return this.name .toUpperCase ()}}constjohn = newPerson ({id : 1,name : "John" })console .log (john .upperName ) // Output: "JOHN"
Using Classes as Schemas
When you define a class using Schema.Class
, it not only creates a new class but also treats this class as a schema.
This means the class can be utilized wherever a schema is expected.
ts
import {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {}// Person can be used as a normal schemaconstPersons =Schema .Array (Person )
ts
import {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {}// Person can be used as a normal schemaconstPersons =Schema .Array (Person )
The fields Property
The class also includes a fields
static property, which outlines the fields defined during the class creation.
ts
import {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {}Person .fields
ts
import {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {}Person .fields
Annotations and Transformations
A class that extends Schema.Class
implicitly forms a schema transformation from a structured type to a class type.
For instance, consider the following definition:
ts
import {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {}
ts
import {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {}
This class definition serves as a transformation from the following struct schema:
ts
Schema.Struct({id: Schema.Number,name: Schema.NonEmptyString})
ts
Schema.Struct({id: Schema.Number,name: Schema.NonEmptyString})
to a schema that represents the Person
class.
Adding Annotations
There are two primary ways to add annotations depending on your requirements:
-
Adding Annotations to the Struct Schema (the "from" part of the transformation):
You can annotate the struct schema component, which is transformed into the class.
tsimport {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")(Schema .Struct ({id :Schema .Number ,name :Schema .NonEmptyString }).annotations ({identifier : "From" })) {}console .log (String (Person .ast )) // Output: (From <-> Person)tsimport {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")(Schema .Struct ({id :Schema .Number ,name :Schema .NonEmptyString }).annotations ({identifier : "From" })) {}console .log (String (Person .ast )) // Output: (From <-> Person) -
Adding Annotations to the Class Schema (the "to" part of the transformation):
Alternatively, annotations can be added directly to the class schema, affecting how the class is represented as a schema.
tsimport {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString },{identifier : "To" }) {}console .log (String (Person .ast )) // Output: (Person (Encoded side) <-> To)tsimport {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString },{identifier : "To" }) {}console .log (String (Person .ast )) // Output: (Person (Encoded side) <-> To)
Recursive Schemas
The Schema.suspend
combinator is useful when you need to define a schema that depends on itself, like in the case of recursive data structures.
In this example, the Category
schema depends on itself because it has a field subcategories
that is an array of Category
objects.
ts
import {Schema } from "@effect/schema"classCategory extendsSchema .Class <Category >("Category")({name :Schema .String ,subcategories :Schema .Array (Schema .suspend (():Schema .Schema <Category > =>Category ))}) {}
ts
import {Schema } from "@effect/schema"classCategory extendsSchema .Class <Category >("Category")({name :Schema .String ,subcategories :Schema .Array (Schema .suspend (():Schema .Schema <Category > =>Category ))}) {}
It is necessary to add an explicit type annotation because otherwise TypeScript would struggle to infer types correctly. Without this annotation, you might encounter the error message:
ts
import {Schema } from "@effect/schema"class'Category' is referenced directly or indirectly in its own base expression.2506'Category' is referenced directly or indirectly in its own base expression.extends Category Schema .Class <Category >("Category")({name :Schema .String ,Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.7024Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.subcategories :Schema .Array (Schema .suspend (() =>Category ))}) {}
ts
import {Schema } from "@effect/schema"class'Category' is referenced directly or indirectly in its own base expression.2506'Category' is referenced directly or indirectly in its own base expression.extends Category Schema .Class <Category >("Category")({name :Schema .String ,Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.7024Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.subcategories :Schema .Array (Schema .suspend (() =>Category ))}) {}
Mutually Recursive Schemas
Here's an example of two mutually recursive schemas, Expression
and Operation
, that represent a simple arithmetic expression tree.
ts
import {Schema } from "@effect/schema"classExpression extendsSchema .Class <Expression >("Expression")({type :Schema .Literal ("expression"),value :Schema .Union (Schema .Number ,Schema .suspend (():Schema .Schema <Operation > =>Operation ))}) {}classOperation extendsSchema .Class <Operation >("Operation")({type :Schema .Literal ("operation"),operator :Schema .Literal ("+", "-"),left :Expression ,right :Expression }) {}
ts
import {Schema } from "@effect/schema"classExpression extendsSchema .Class <Expression >("Expression")({type :Schema .Literal ("expression"),value :Schema .Union (Schema .Number ,Schema .suspend (():Schema .Schema <Operation > =>Operation ))}) {}classOperation extendsSchema .Class <Operation >("Operation")({type :Schema .Literal ("operation"),operator :Schema .Literal ("+", "-"),left :Expression ,right :Expression }) {}
Recursive Types with Different Encoded and Type
Defining a recursive schema where the Encoded
type differs from the Type
type adds another layer of complexity.
In such cases, we need to define an interface for the Encoded
type.
Let's consider an example: suppose we want to add an id
field to the Category
schema, where the schema for id
is NumberFromString
.
It's important to note that NumberFromString
is a schema that transforms a string into a number, so the Type
and Encoded
types of NumberFromString
differ, being number
and string
respectively.
When we add this field to the Category
schema, TypeScript raises an error:
ts
import {Schema } from "@effect/schema"classCategory extendsSchema .Class <Category >("Category")({id :Schema .NumberFromString ,name :Schema .String ,subcategories :Schema .Array (Type 'typeof Category' is not assignable to type 'Schema<Category, Category, never>'. The types of 'Encoded.id' are incompatible between these types. Type 'string' is not assignable to type 'number'.2322Type 'typeof Category' is not assignable to type 'Schema<Category, Category, never>'. The types of 'Encoded.id' are incompatible between these types. Type 'string' is not assignable to type 'number'.Schema .suspend (():Schema .Schema <Category > =>) Category )}) {}
ts
import {Schema } from "@effect/schema"classCategory extendsSchema .Class <Category >("Category")({id :Schema .NumberFromString ,name :Schema .String ,subcategories :Schema .Array (Type 'typeof Category' is not assignable to type 'Schema<Category, Category, never>'. The types of 'Encoded.id' are incompatible between these types. Type 'string' is not assignable to type 'number'.2322Type 'typeof Category' is not assignable to type 'Schema<Category, Category, never>'. The types of 'Encoded.id' are incompatible between these types. Type 'string' is not assignable to type 'number'.Schema .suspend (():Schema .Schema <Category > =>) Category )}) {}
This error occurs because the explicit annotation S.suspend((): S.Schema<Category> => Category
is no longer sufficient and needs to be adjusted by explicitly adding the Encoded
type:
ts
import {Schema } from "@effect/schema"interfaceCategoryEncoded {readonlyid : stringreadonlyname : stringreadonlysubcategories :ReadonlyArray <CategoryEncoded >}classCategory extendsSchema .Class <Category >("Category")({id :Schema .NumberFromString ,name :Schema .String ,subcategories :Schema .Array (Schema .suspend (():Schema .Schema <Category ,CategoryEncoded > =>Category ))}) {}
ts
import {Schema } from "@effect/schema"interfaceCategoryEncoded {readonlyid : stringreadonlyname : stringreadonlysubcategories :ReadonlyArray <CategoryEncoded >}classCategory extendsSchema .Class <Category >("Category")({id :Schema .NumberFromString ,name :Schema .String ,subcategories :Schema .Array (Schema .suspend (():Schema .Schema <Category ,CategoryEncoded > =>Category ))}) {}
As we've observed, it's necessary to define an interface for the Encoded
of the schema to enable recursive schema definition, which can complicate things and be quite tedious.
One pattern to mitigate this is to separate the field responsible for recursion from all other fields.
ts
import {Schema } from "@effect/schema"constfields = {id :Schema .NumberFromString ,name :Schema .String // ...possibly other fields}interfaceCategoryEncoded extendsSchema .Struct .Encoded <typeoffields > {// Define `subcategories` using recursionreadonlysubcategories :ReadonlyArray <CategoryEncoded >}classCategory extendsSchema .Class <Category >("Category")({...fields , // Include the fieldssubcategories :Schema .Array (// Define `subcategories` using recursionSchema .suspend (():Schema .Schema <Category ,CategoryEncoded > =>Category ))}) {}
ts
import {Schema } from "@effect/schema"constfields = {id :Schema .NumberFromString ,name :Schema .String // ...possibly other fields}interfaceCategoryEncoded extendsSchema .Struct .Encoded <typeoffields > {// Define `subcategories` using recursionreadonlysubcategories :ReadonlyArray <CategoryEncoded >}classCategory extendsSchema .Class <Category >("Category")({...fields , // Include the fieldssubcategories :Schema .Array (// Define `subcategories` using recursionSchema .suspend (():Schema .Schema <Category ,CategoryEncoded > =>Category ))}) {}
Tagged Class variants
You can also create classes that extend TaggedClass and TaggedError from the effect/Data
module.
Example
ts
import {Schema } from "@effect/schema"classTaggedPerson extendsSchema .TaggedClass <TaggedPerson >()("TaggedPerson",{name :Schema .String }) {}classHttpError extendsSchema .TaggedError <HttpError >()("HttpError", {status :Schema .Number }) {}constjoe = newTaggedPerson ({name : "Joe" })console .log (joe ._tag ) // "TaggedPerson"consterror = newHttpError ({status : 404 })console .log (error ._tag ) // "HttpError"console .log (error .stack ) // access the stack trace
ts
import {Schema } from "@effect/schema"classTaggedPerson extendsSchema .TaggedClass <TaggedPerson >()("TaggedPerson",{name :Schema .String }) {}classHttpError extendsSchema .TaggedError <HttpError >()("HttpError", {status :Schema .Number }) {}constjoe = newTaggedPerson ({name : "Joe" })console .log (joe ._tag ) // "TaggedPerson"consterror = newHttpError ({status : 404 })console .log (error ._tag ) // "HttpError"console .log (error .stack ) // access the stack trace
Extending existing Classes
In situations where you need to augment your existing class with more fields, the built-in extend
static utility comes in handy.
Example
ts
import {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {getupperName () {return this.name .toUpperCase ()}}classPersonWithAge extendsPerson .extend <PersonWithAge >("PersonWithAge")({age :Schema .Number }) {getisAdult () {return this.age >= 18}}
ts
import {Schema } from "@effect/schema"classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .NonEmptyString }) {getupperName () {return this.name .toUpperCase ()}}classPersonWithAge extendsPerson .extend <PersonWithAge >("PersonWithAge")({age :Schema .Number }) {getisAdult () {return this.age >= 18}}
Transformations
You have the option to enhance a class with (effectful) transformations. This becomes valuable when you want to enrich or validate an entity sourced from a data store.
ts
import {Schema ,ParseResult } from "@effect/schema"import {Effect ,Option } from "effect"export classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .String }) {}console .log (Schema .decodeUnknownSync (Person )({id : 1,name : "name" }))/*Output:Person { id: 1, name: 'name' }*/functiongetAge (id : number):Effect .Effect <number,Error > {returnEffect .succeed (id + 2)}export classPersonWithTransform extendsPerson .transformOrFail <PersonWithTransform >("PersonWithTransform")({age :Schema .optionalWith (Schema .Number , {exact : true,as : "Option" })},{decode : (input ) =>Effect .mapBoth (getAge (input .id ), {onFailure : (e ) =>newParseResult .Type (Schema .String .ast ,input .id ,e .message ),// must return { age: Option<number> }onSuccess : (age ) => ({ ...input ,age :Option .some (age ) })}),encode :ParseResult .succeed }) {}Schema .decodeUnknownPromise (PersonWithTransform )({id : 1,name : "name"}).then (console .log )/*Output:PersonWithTransform {id: 1,name: 'name',age: { _id: 'Option', _tag: 'Some', value: 3 }}*/export classPersonWithTransformFrom extendsPerson .transformOrFailFrom <PersonWithTransformFrom >("PersonWithTransformFrom")({age :Schema .optionalWith (Schema .Number , {exact : true,as : "Option" })},{decode : (input ) =>Effect .mapBoth (getAge (input .id ), {onFailure : (e ) =>newParseResult .Type (Schema .String .ast ,input ,e .message ),// must return { age?: number }onSuccess : (age ) => (age > 18 ? { ...input ,age } : { ...input })}),encode :ParseResult .succeed }) {}Schema .decodeUnknownPromise (PersonWithTransformFrom )({id : 1,name : "name"}).then (console .log )/*Output:PersonWithTransformFrom {id: 1,name: 'name',age: { _id: 'Option', _tag: 'None' }}*/
ts
import {Schema ,ParseResult } from "@effect/schema"import {Effect ,Option } from "effect"export classPerson extendsSchema .Class <Person >("Person")({id :Schema .Number ,name :Schema .String }) {}console .log (Schema .decodeUnknownSync (Person )({id : 1,name : "name" }))/*Output:Person { id: 1, name: 'name' }*/functiongetAge (id : number):Effect .Effect <number,Error > {returnEffect .succeed (id + 2)}export classPersonWithTransform extendsPerson .transformOrFail <PersonWithTransform >("PersonWithTransform")({age :Schema .optionalWith (Schema .Number , {exact : true,as : "Option" })},{decode : (input ) =>Effect .mapBoth (getAge (input .id ), {onFailure : (e ) =>newParseResult .Type (Schema .String .ast ,input .id ,e .message ),// must return { age: Option<number> }onSuccess : (age ) => ({ ...input ,age :Option .some (age ) })}),encode :ParseResult .succeed }) {}Schema .decodeUnknownPromise (PersonWithTransform )({id : 1,name : "name"}).then (console .log )/*Output:PersonWithTransform {id: 1,name: 'name',age: { _id: 'Option', _tag: 'Some', value: 3 }}*/export classPersonWithTransformFrom extendsPerson .transformOrFailFrom <PersonWithTransformFrom >("PersonWithTransformFrom")({age :Schema .optionalWith (Schema .Number , {exact : true,as : "Option" })},{decode : (input ) =>Effect .mapBoth (getAge (input .id ), {onFailure : (e ) =>newParseResult .Type (Schema .String .ast ,input ,e .message ),// must return { age?: number }onSuccess : (age ) => (age > 18 ? { ...input ,age } : { ...input })}),encode :ParseResult .succeed }) {}Schema .decodeUnknownPromise (PersonWithTransformFrom )({id : 1,name : "name"}).then (console .log )/*Output:PersonWithTransformFrom {id: 1,name: 'name',age: { _id: 'Option', _tag: 'None' }}*/
The decision of which API to use, either transformOrFail
or transformOrFailFrom
, depends on when you wish to execute the transformation:
-
Using
transformOrFail
:- The transformation occurs at the end of the process.
- It expects you to provide a value of type
{ age: Option<number> }
. - After processing the initial input, the new transformation comes into play, and you need to ensure the final output adheres to the specified structure.
-
Using
transformOrFailFrom
:- The new transformation starts as soon as the initial input is handled.
- You should provide a value
{ age?: number }
. - Based on this fresh input, the subsequent transformation
{ age: Schema.optionalToOption(S.Number, { exact: true }) }
is executed. - This approach allows for immediate handling of the input, potentially influencing the subsequent transformations.