Skip to main content

Process variant data

In this tutorial, you will perform a calculation based on VaraintType data. You will

  • define a writeable VaraintType data source, and
  • define a function with FunctionBuilder to perform calculations based on variant data using the Match expression, and
  • launch a solution, interact with and validate your calculations.

This lesson will assume that you have an empty project and asset which you can to deploy to a workspace named 06_04_03_use_varrianttype_data with the following command:

edk template deploy -ycw 06_04_03_use_varrianttype_data

The area calculation

This tutorial extends

the previous by performing a calculation on the variant data. The source data will be a variant type representing different geometric objects. There are three cases describing three kinds of geometry: points, circles and rectangles.

const GeometryType = VariantType({
point: NullType,
circle: FloatType,
rectangle: StructType({
width: FloatType,
height: FloatType,
}),
})

The goal is to perform a calculation of the area of each type of geometry. The calculation will depend on which shape it is, and the dimensions of the shape:

  • A point by definition has zero area.
  • The area of a circle is the mathematical constant pi (3.14159...) multiplied by the radius squared.
  • The area of a rectangle is the width multiplied by the height.

Variants types are ideal for representing various cases that might require different logic. The Match expression is the primary way of performing such logic on a case-by-case basis. To perform the logic described above on an a value geometry of type GeometryType, we can use the following East expression:

Match(
geometry,
{
point: _ => Const(0.0),
circle: radius => Multiply(Math.PI, Multiply(radius, radius)),
rectangle: dimensions => Multiply(
GetField(dimensions, "width"),
GetField(dimensions, "height"),
)
}
)

The Match expression returns a result that depends on the case of geometry. Each expression is a function of the data associated with that case. For the point case we return the constant 0.0 (we ignore the null associated data by naming it _, which is convention for a variable we don't care about). For the circle case we multiply pi with the radius times the radius. For the rectangle case we multiply the width and the height of the rectangle, which were stored in a struct associated with this case.

Next you'll create a project that performs this calculation in a function.

Preparation

Create a directory in the project root called data, and within the directory place a file called point.json to write into the data stream. Place the following content into the file:

{
"type": "point",
"value": null
}

Also create a file called circle.json with this data.

{
"type": "circle",
"value": 10
}

Finally create a file called rectangle.json with this data.

{
"type": "rectangle",
"value": { "width": 5, "height": 4 }
}

Define the data source

Start where you left off in the previous tutorial, with a writeable data source for GeometryType data.

import { VariantType, FloatType, NullType, SourceBuilder, StructType, Template } from "@elaraai/core"

const GeometryType = VariantType({
point: NullType,
circle: FloatType,
rectangle: StructType({
width: FloatType,
height: FloatType,
}),
})

const my_source = new SourceBuilder("My Source")
.writeable(GeometryType);

export default Template(my_source)

Add the function to calculate area

Now we add a function using FunctionBuilder. If necessary, refresh your memory by revising the

tutorial on functions.

In this function we'll import the data source as an input named "geometry". Next we use .let to create a variable containing the calculated area, using the expression devised earlier. Finally we return an output called "area" based on the result.

The full sollutions is shown below:

import { Const, FloatType, FunctionBuilder, GetField, Match, Multiply, NullType, SourceBuilder, StructType, Template, VariantType } from "@elaraai/core"

const GeometryType = VariantType({
point: NullType,
circle: FloatType,
rectangle: StructType({
width: FloatType,
height: FloatType,
})
})

const my_source = new SourceBuilder("My Source")
.writeable(GeometryType);

const area = new FunctionBuilder("Area")
.input("geometry", my_source.outputStream())
.body(block => block
.let("area", vars => Match(vars.geometry, {
point: _ => Const(0.0),
circle: radius => Multiply(Math.PI, Multiply(radius, radius)),
rectangle: dimensions => Multiply(
GetField(dimensions, "width"),
GetField(dimensions, "height"),
)
}))
.return({
area: vars => vars.area,
})
)

export default Template(my_source, area);

Observe the resulting area calculation

First build the template, and then deploy it with:

edk template deploy -ycw 06_04_03_use_varianttype_data

Points

Write the contents of data/point.json to your data stream with the following command:

edk stream replace "Writeable.My Source" --file data/point.json -w 06_04_03_use_varianttype_data

You can validate to contents of the area calculation by using the edk stream get command:

edk stream get Function.Area.area -w 06_04_03_use_varianttype_data

Which will result area in std out:

▹▹▹▹▹ Attempting to stream Function.Area.area to stdout
0
✔ Download complete

As expected, the area of a point is zero.

Circles

Write the contents of data/circle.json to your data stream with the following command:

edk stream replace "Writeable.My Source" --file data/circle.json -w 06_04_03_use_varianttype_data

Now fetch the result of the area calculation by using the edk stream get command:

edk stream get Function.Area.area -w 06_04_03_use_varianttype_data

The result is:

▹▹▹▹▹ Attempting to stream Function.Area.area to stdout
314.1592653589793
✔ Download complete

The result is pi times 100, which is what we expect for a circle of radius 10.

Rectangles

Write the contents of data/rectangle.json to your data stream with the following command:

edk stream replace "Writeable.My Source" --file data/rectangle.json -w 06_04_03_use_varianttype_data

Now fetch the result of the area calculation by using the edk stream get command:

edk stream get Function.Area.area -w 06_04_03_use_varianttype_data

The result is:

▹▹▹▹▹ Attempting to stream Function.Area.area to stdout
20
✔ Download complete

The result is the width 5 multiplied by the height 4, which is 20.

Additional exercises

Suppose we wanted to add a new geometry type for a square. How would you modify the type? How would you modify the area calculations? Optionally, make this change to your project and try to write a square to the data stream and confirm the results of the area calculation are correct.

Next suppose we wanted to calculate the perimeter as well as the area of the geometries in our function. How would you modify the FunctionBuilder in order to calculate and return the perimeter? Optionally, make this change to your project and confirm the results of the perimeter calculations are correct with the various geometry cases.

Example Solution

The final solution for this tutorial is available below:

Next Steps

In this tutorial, you learned how to transform variants in an expression as a part of a function. This concludes the module on variants and the topic of working with complex data.