---
title: "Expressions Tutorial: Operation Expressions Code Example"
description: "A full runnable Java, Python, Go, C#, Node.js, and Rust example that computes grades and a report server-side with operation expressions."
---

# Operation expressions code example

> For the complete documentation index see: [llms.txt](https://aerospike.com/docs/llms.txt)
> 
> All documentation pages available in markdown.

This page is for developers using Aerospike client libraries. Complete [Setup](https://aerospike.com/docs/develop/tutorials/operations/expressions/setup) first. After working through this example, you can compute grades and a report server-side with operation expressions.

In the following example, these new capabilities that were not possible earlier are illustrated:

-   expressions involving zero or more bins to write a bin
-   named “computed bins” that return the value of a specified expression involving zero or more bins
-   conditional evaluation of expression
-   use of variables in an expression

The code has the following steps:

1.  The test data is populated with three randomly generated test scores ranging from 50 to 99 for student ids 1-20.
2.  The data is updated by writing two additional bins: “class” which represents the teacher’s input (0-9) based on class participation, and “grade” which is computed by adding “classwork” to average of test scores, and using this formula to compute the grade: scores below 70 receive C; scores from 70 through 85 receive B; scores above 85 receive A.
3.  A report is then generated for the id, grade, total score, and min/max/average of test scores.

-   [Java](#tab-panel-5649)
-   [Python](#tab-panel-5650)
-   [Go](#tab-panel-5651)
-   [C#](#tab-panel-5652)
-   [Node.js](#tab-panel-5653)
-   [Rust](#tab-panel-5654)

The following snippets assume the client setup from [Setup](https://aerospike.com/docs/develop/tutorials/operations/expressions/setup).

```java
import com.aerospike.client.exp.Expression;

import com.aerospike.client.exp.ExpOperation;

import com.aerospike.client.exp.ExpReadFlags;

import com.aerospike.client.exp.ExpWriteFlags;

// start with a clean state

truncateTestData();

// 1. The test data is populated with three randomly generated test scores ranging from 50 to 100

//    for student ids 1-20.

Random rand = new Random(1);

final int SCORE_RANGE = 50;

WritePolicy wpolicy = copyWritePolicyDefault();

wpolicy.sendKey = true;

for (int i = 1; i <= 20; i++) {

    Key key = new Key(NAMESPACE, SET_NAME, "id-"+i);

    Bin id = new Bin("id", i);

    List<Integer> testScores = List.of(

        50 + rand.nextInt(SCORE_RANGE),

        50 + rand.nextInt(SCORE_RANGE),

        50 + rand.nextInt(SCORE_RANGE));

    Bin tests = new Bin("tests", testScores);

    client.put(wpolicy, key, id, tests);

}

System.out.println("Test data populated.");

// 2. The data is updated by writing two additional bins: "class" which represents the teacher's input (0-10)

//    based on class participation, and "grade" which is computed by adding "classwork" to average of test scores,

//    and using this formula: 50-70 -> C, 65==70-85 -> B, 85+ -> A.

// define the expressions for average test score, total score, grade, min test score, and max test score

Exp avgScoreExp = Exp.let(

    Exp.def("testsBin", Exp.listBin("tests")),

    Exp.div(

        Exp.add(

            ListExp.getByIndex(ListReturnType.VALUE, Exp.Type.INT, Exp.val(0), Exp.var("testsBin")),

            ListExp.getByIndex(ListReturnType.VALUE, Exp.Type.INT, Exp.val(1), Exp.var("testsBin")),

            ListExp.getByIndex(ListReturnType.VALUE, Exp.Type.INT, Exp.val(2), Exp.var("testsBin"))),

        Exp.val(3)));

Expression avgScoreExpression = Exp.build(avgScoreExp);

Exp totalScoreExp = Exp.add(avgScoreExp, Exp.intBin("class"));

Expression totalScoreExpression = Exp.build(totalScoreExp);

Exp gradeExp = Exp.let(

                    Exp.def("total", totalScoreExp),

                    Exp.cond(

                        Exp.lt(Exp.var("total"), Exp.val(70)), Exp.val("C"),

                        Exp.le(Exp.var("total"), Exp.val(85)), Exp.val("B"),

                        Exp.gt(Exp.var("total"), Exp.val(85)), Exp.val("A"),

                        Exp.unknown()));

Expression gradeExpression = Exp.build(gradeExp);

Exp minExp = ListExp.getByRank(ListReturnType.VALUE, Exp.Type.INT, Exp.val(0), Exp.listBin("tests"));   // min

Expression minExpression = Exp.build(minExp);

Exp maxExp = ListExp.getByRank(ListReturnType.VALUE, Exp.Type.INT, Exp.val(-1), Exp.listBin("tests"));   // max

Expression maxExpression = Exp.build(maxExp);

// update class bin with a random 0-9, and the grade bin using gradeExpOp

for (int i = 1; i <= 20; i++) {

    Key key = new Key(NAMESPACE, SET_NAME, "id-"+i);

    int classwork = rand.nextInt(SCORE_RANGE)/5;

    Bin classBin = new Bin(new String("class"), classwork);

    // write gradeExp to the bin "grade"

    Operation gradeExpOp = ExpOperation.write("grade",

                                 gradeExpression,

                                 ExpWriteFlags.DEFAULT);

    client.operate(wpolicy, key, Operation.put(classBin), gradeExpOp);

}

System.out.println("Updated class participation and computed grades.");;

// run a report for all students with id, grade, total, min, max, avg

// using total, min, max, and avg expressions defined above

Operation ops[] = { Operation.get("id"),

                    Operation.get("grade"),

                    ExpOperation.read("total", totalScoreExpression, ExpReadFlags.DEFAULT),

                    ExpOperation.read("testMin", minExpression, ExpReadFlags.DEFAULT),

                    ExpOperation.read("testMax", maxExpression, ExpReadFlags.DEFAULT),

                    ExpOperation.read("testAvg", avgScoreExpression, ExpReadFlags.DEFAULT)

};

for (int i = 1; i <= 20; i++) {

    Key key = new Key(NAMESPACE, SET_NAME, "id-"+i);

    Record record = client.operate(null, key, ops);

    System.out.format("key=%s bins=%s\n", key.userKey, record.bins);

}
```

Output:

```text
Test data populated.

Updated class participation and computed grades.

key=id-1 bins={id=1, grade=A, total=90, testMin=85, testMax=97, testAvg=90}

key=id-2 bins={id=2, grade=C, total=57, testMin=54, testMax=63, testAvg=57}

key=id-3 bins={id=3, grade=B, total=79, testMin=56, testMax=84, testAvg=72}

key=id-4 bins={id=4, grade=A, total=86, testMin=69, testMax=98, testAvg=80}

key=id-5 bins={id=5, grade=B, total=73, testMin=62, testMax=67, testAvg=64}

key=id-6 bins={id=6, grade=A, total=88, testMin=62, testMax=92, testAvg=79}

key=id-7 bins={id=7, grade=A, total=88, testMin=76, testMax=96, testAvg=87}

key=id-8 bins={id=8, grade=B, total=82, testMin=60, testMax=99, testAvg=80}

key=id-9 bins={id=9, grade=B, total=80, testMin=59, testMax=98, testAvg=77}

key=id-10 bins={id=10, grade=C, total=69, testMin=52, testMax=87, testAvg=64}

key=id-11 bins={id=11, grade=C, total=54, testMin=50, testMax=55, testAvg=53}

key=id-12 bins={id=12, grade=C, total=61, testMin=55, testMax=63, testAvg=58}

key=id-13 bins={id=13, grade=B, total=81, testMin=70, testMax=89, testAvg=78}

key=id-14 bins={id=14, grade=B, total=70, testMin=60, testMax=84, testAvg=69}

key=id-15 bins={id=15, grade=B, total=84, testMin=77, testMax=87, testAvg=80}

key=id-16 bins={id=16, grade=A, total=90, testMin=70, testMax=92, testAvg=83}

key=id-17 bins={id=17, grade=C, total=61, testMin=50, testMax=57, testAvg=53}

key=id-18 bins={id=18, grade=B, total=77, testMin=55, testMax=83, testAvg=71}

key=id-19 bins={id=19, grade=B, total=79, testMin=58, testMax=95, testAvg=79}

key=id-20 bins={id=20, grade=B, total=81, testMin=54, testMax=84, testAvg=72}
```

This assumes the same setup as the Java example above, translated to the classic Python client: `client = aerospike.client(config).connect()`, with `NAMESPACE = "test"` and `SET_NAME = "expressions"`.

```python
import random

from aerospike_helpers import expressions as exp

from aerospike_helpers.expressions.list import ListGetByIndex, ListGetByRank

from aerospike_helpers.operations import expression_operations, operations

# start with a clean state

truncate_test_data()

# 1. The test data is populated with three randomly generated test scores ranging from 50 to 100

#    for student ids 1-20.

random.seed(1)

SCORE_RANGE = 50

write_policy = {"key": aerospike.POLICY_KEY_SEND}

for i in range(1, 21):

    key = (NAMESPACE, SET_NAME, "id-" + str(i))

    test_scores = [50 + random.randint(0, SCORE_RANGE - 1) for _ in range(3)]

    client.put(key, {"id": i, "tests": test_scores}, policy=write_policy)

print("Test data populated.")

# 2. The data is updated by writing two additional bins: "class" which represents the teacher's input (0-10)

#    based on class participation, and "grade" which is computed by adding "classwork" to average of test scores,

#    and using this formula: 50-70 -> C, 65==70-85 -> B, 85+ -> A.

# define the expressions for average test score, total score, grade, min test score, and max test score

avg_score_exp = exp.Let(

    exp.Def("testsBin", exp.ListBin("tests")),

    exp.Div(

        exp.Add(

            ListGetByIndex(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, 0, exp.Var("testsBin")),

            ListGetByIndex(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, 1, exp.Var("testsBin")),

            ListGetByIndex(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, 2, exp.Var("testsBin")),

        ),

        3,

    ),

)

avg_score_expression = avg_score_exp.compile()

total_score_exp = exp.Let(

    exp.Def("classBin", exp.IntBin("class")),

    exp.Add(avg_score_exp, exp.Var("classBin")),

)

total_score_expression = total_score_exp.compile()

grade_exp = exp.Let(

    exp.Def("total", total_score_exp),

    exp.Cond(

        exp.LT(exp.Var("total"), 70), "C",

        exp.LE(exp.Var("total"), 85), "B",

        exp.GT(exp.Var("total"), 85), "A",

        exp.Unknown(),

    ),

)

grade_expression = grade_exp.compile()

min_exp = ListGetByRank(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, 0, exp.ListBin("tests"))    # min

min_expression = min_exp.compile()

max_exp = ListGetByRank(None, aerospike.LIST_RETURN_VALUE, exp.ResultType.INTEGER, -1, exp.ListBin("tests"))   # max

max_expression = max_exp.compile()

# update class bin with a random 1-10, and the grade bin using gradeExpression

for i in range(1, 21):

    key = (NAMESPACE, SET_NAME, "id-" + str(i))

    classwork = random.randint(0, SCORE_RANGE - 1) // 5

    # write gradeExpression to the bin "grade"

    ops = [

        operations.write("class", classwork),

        expression_operations.expression_write("grade", grade_expression, aerospike.EXP_WRITE_DEFAULT),

    ]

    client.operate(key, ops)

print("Updated class participation and computed grades.")

# run a report for all students with id, grade, total, min, max, avg

# using total, min, max, and avg expressions defined above

ops = [

    operations.read("id"),

    operations.read("grade"),

    expression_operations.expression_read("total", total_score_expression, aerospike.EXP_READ_DEFAULT),

    expression_operations.expression_read("testMin", min_expression, aerospike.EXP_READ_DEFAULT),

    expression_operations.expression_read("testMax", max_expression, aerospike.EXP_READ_DEFAULT),

    expression_operations.expression_read("testAvg", avg_score_expression, aerospike.EXP_READ_DEFAULT),

]

for i in range(1, 21):

    key = (NAMESPACE, SET_NAME, "id-" + str(i))

    _, _, bins = client.operate(key, ops)

    print("key={} bins={}".format(key[2], bins))
```

::: note
Output values depend on randomly generated test data. Python’s `random` module and Java’s `java.util.Random` produce different sequences even from the same seed, so the printed grades, totals, and test scores will differ from the Java output above, though the shape of the result is the same.
:::

This assumes the same setup as the Java example above, translated to the Go client: `client, err := as.NewClient("localhost", 3000)`, with `NAMESPACE = "test"` and `SET_NAME = "expressions"`.

```go
import (

    "fmt"

    "log"

    "math/rand"

    as "github.com/aerospike/aerospike-client-go/v6"

)

// start with a clean state

truncateTestData()

// 1. The test data is populated with three randomly generated test scores ranging from 50 to 100

//    for student ids 1-20.

r := rand.New(rand.NewSource(1))

const ScoreRange = 50

wpolicy := as.NewWritePolicy(0, 0)

wpolicy.SendKey = true

for i := 1; i <= 20; i++ {

    key, _ := as.NewKey(NAMESPACE, SET_NAME, fmt.Sprintf("id-%d", i))

    tests := []interface{}{

        50 + r.Intn(ScoreRange),

        50 + r.Intn(ScoreRange),

        50 + r.Intn(ScoreRange),

    }

    if err := client.Put(wpolicy, key,

        as.NewBin("id", i),

        as.NewBin("tests", tests)); err != nil {

        log.Fatal(err)

    }

}

fmt.Println("Test data populated.")

// 2. The data is updated by writing two additional bins: "class" which represents the teacher's input (0-10)

//    based on class participation, and "grade" which is computed by adding "classwork" to average of test scores,

//    and using this formula: 50-70 -> C, 65==70-85 -> B, 85+ -> A.

// define the expressions for average test score, total score, grade, min test score, and max test score

avgScoreExp := as.ExpLet(

    as.ExpDef("testsBin", as.ExpListBin("tests")),

    as.ExpNumDiv(

        as.ExpNumAdd(

            as.ExpListGetByIndex(as.ListReturnTypeValue, as.ExpTypeINT, as.ExpIntVal(0), as.ExpVar("testsBin")),

            as.ExpListGetByIndex(as.ListReturnTypeValue, as.ExpTypeINT, as.ExpIntVal(1), as.ExpVar("testsBin")),

            as.ExpListGetByIndex(as.ListReturnTypeValue, as.ExpTypeINT, as.ExpIntVal(2), as.ExpVar("testsBin"))),

        as.ExpIntVal(3)))

totalScoreExp := as.ExpLet(

    as.ExpDef("classBin", as.ExpIntBin("class")),

    as.ExpNumAdd(avgScoreExp, as.ExpVar("classBin")))

gradeExp := as.ExpLet(

    as.ExpDef("total", totalScoreExp),

    as.ExpCond(

        as.ExpLess(as.ExpVar("total"), as.ExpIntVal(70)), as.ExpStringVal("C"),

        as.ExpLessEq(as.ExpVar("total"), as.ExpIntVal(85)), as.ExpStringVal("B"),

        as.ExpGreater(as.ExpVar("total"), as.ExpIntVal(85)), as.ExpStringVal("A"),

        as.ExpUnknown()))

minExp := as.ExpListGetByRank(as.ListReturnTypeValue, as.ExpTypeINT, as.ExpIntVal(0), as.ExpListBin("tests"))    // min

maxExp := as.ExpListGetByRank(as.ListReturnTypeValue, as.ExpTypeINT, as.ExpIntVal(-1), as.ExpListBin("tests"))   // max

// update class bin with a random 1-10, and the grade bin using gradeExp

for i := 1; i <= 20; i++ {

    key, _ := as.NewKey(NAMESPACE, SET_NAME, fmt.Sprintf("id-%d", i))

    classwork := r.Intn(ScoreRange) / 5

    // write gradeExp to the bin "grade"

    gradeExpOp := as.ExpWriteOp("grade", gradeExp, as.ExpWriteFlagDefault)

    if _, err := client.Operate(wpolicy, key,

        as.PutOp(as.NewBin("class", classwork)),

        gradeExpOp); err != nil {

        log.Fatal(err)

    }

}

fmt.Println("Updated class participation and computed grades.")

// run a report for all students with id, grade, total, min, max, avg

// using total, min, max, and avg expressions defined above

ops := []*as.Operation{

    as.GetBinOp("id"),

    as.GetBinOp("grade"),

    as.ExpReadOp("total", totalScoreExp, as.ExpReadFlagDefault),

    as.ExpReadOp("testMin", minExp, as.ExpReadFlagDefault),

    as.ExpReadOp("testMax", maxExp, as.ExpReadFlagDefault),

    as.ExpReadOp("testAvg", avgScoreExp, as.ExpReadFlagDefault),

}

for i := 1; i <= 20; i++ {

    key, _ := as.NewKey(NAMESPACE, SET_NAME, fmt.Sprintf("id-%d", i))

    record, err := client.Operate(nil, key, ops...)

    if err != nil {

        log.Fatal(err)

    }

    fmt.Printf("key=%v bins=%v\n", record.Key.Value(), record.Bins)

}
```

::: note
Output values depend on randomly generated test data. Go’s `math/rand` produces a different sequence than Python’s `random` or Java’s `java.util.Random` even from the same seed, so the printed grades, totals, and test scores will differ from the Java output above, though the shape of the result is the same.
:::

This assumes the same setup as the Java example above, translated to the C# client: `AerospikeClient client = new AerospikeClient(null, host)`, with `NAMESPACE = "test"` and `SET_NAME = "expressions"`.

```csharp
using Aerospike.Client;

// start with a clean state

TruncateTestData();

// 1. The test data is populated with three randomly generated test scores ranging from 50 to 100

//    for student ids 1-20.

Random rand = new Random(1);

const int ScoreRange = 50;

WritePolicy wpolicy = new WritePolicy();

wpolicy.sendKey = true;

for (int i = 1; i <= 20; i++)

{

    Key key = new Key(NAMESPACE, SET_NAME, "id-" + i);

    Bin id = new Bin("id", i);

    List<int> testScores = new List<int> {

        50 + rand.Next(ScoreRange), 50 + rand.Next(ScoreRange), 50 + rand.Next(ScoreRange)

    };

    Bin tests = new Bin("tests", testScores);

    client.Put(wpolicy, key, id, tests);

}

Console.WriteLine("Test data populated.");

// 2. The data is updated by writing two additional bins: "class" which represents the teacher's input (0-10)

//    based on class participation, and "grade" which is computed by adding "classwork" to average of test scores,

//    and using this formula: 50-70 -> C, 65==70-85 -> B, 85+ -> A.

// define the expressions for average test score, total score, grade, min test score, and max test score

Exp avgScoreExp = Exp.Let(

    Exp.Def("testsBin", Exp.ListBin("tests")),

    Exp.Div(

        Exp.Add(

            ListExp.GetByIndex(ListReturnType.VALUE, Exp.Type.INT, Exp.Val(0), Exp.Var("testsBin")),

            ListExp.GetByIndex(ListReturnType.VALUE, Exp.Type.INT, Exp.Val(1), Exp.Var("testsBin")),

            ListExp.GetByIndex(ListReturnType.VALUE, Exp.Type.INT, Exp.Val(2), Exp.Var("testsBin"))),

        Exp.Val(3)));

Expression avgScoreExpression = Exp.Build(avgScoreExp);

Exp totalScoreExp = Exp.Let(

    Exp.Def("classBin", Exp.IntBin("class")),

    Exp.Add(avgScoreExp, Exp.Var("classBin")));

Expression totalScoreExpression = Exp.Build(totalScoreExp);

Exp gradeExp = Exp.Let(

    Exp.Def("total", totalScoreExp),

    Exp.Cond(

        Exp.LT(Exp.Var("total"), Exp.Val(70)), Exp.Val("C"),

        Exp.LE(Exp.Var("total"), Exp.Val(85)), Exp.Val("B"),

        Exp.GT(Exp.Var("total"), Exp.Val(85)), Exp.Val("A"),

        Exp.Unknown()));

Expression gradeExpression = Exp.Build(gradeExp);

Exp minExp = ListExp.GetByRank(ListReturnType.VALUE, Exp.Type.INT, Exp.Val(0), Exp.ListBin("tests"));    // min

Expression minExpression = Exp.Build(minExp);

Exp maxExp = ListExp.GetByRank(ListReturnType.VALUE, Exp.Type.INT, Exp.Val(-1), Exp.ListBin("tests"));   // max

Expression maxExpression = Exp.Build(maxExp);

// update class bin with a random 1-10, and the grade bin using gradeExpression

for (int i = 1; i <= 20; i++)

{

    Key key = new Key(NAMESPACE, SET_NAME, "id-" + i);

    int classwork = rand.Next(ScoreRange) / 5;

    Bin classBin = new Bin("class", classwork);

    // write gradeExpression to the bin "grade"

    Operation gradeExpOp = ExpOperation.Write("grade",

        gradeExpression,

        ExpWriteFlags.DEFAULT);

    client.Operate(wpolicy, key, Operation.Put(classBin), gradeExpOp);

}

Console.WriteLine("Updated class participation and computed grades.");

// run a report for all students with id, grade, total, min, max, avg

// using total, min, max, and avg expressions defined above

Operation[] ops = {

    Operation.Get("id"),

    Operation.Get("grade"),

    ExpOperation.Read("total", totalScoreExpression, ExpReadFlags.DEFAULT),

    ExpOperation.Read("testMin", minExpression, ExpReadFlags.DEFAULT),

    ExpOperation.Read("testMax", maxExpression, ExpReadFlags.DEFAULT),

    ExpOperation.Read("testAvg", avgScoreExpression, ExpReadFlags.DEFAULT)

};

for (int i = 1; i <= 20; i++)

{

    Key key = new Key(NAMESPACE, SET_NAME, "id-" + i);

    Record record = client.Operate(null, key, ops);

    Console.WriteLine("key={0} bins={1}", key.userKey, record.ToString().Split("bins:")[1]);

}
```

::: note
Output values depend on randomly generated test data. .NET’s `Random` produces a different sequence than Python’s `random`, Java’s `java.util.Random`, or Go’s `math/rand` even from the same seed, so the printed grades, totals, and test scores will differ from the Java output above, though the shape of the result is the same.
:::

This assumes the same setup as the Java example above, translated to the Node.js client: `const client = await Aerospike.connect(config)`, with `NAMESPACE = 'test'` and `SET_NAME = 'expressions'`.

```js
const Aerospike = require('aerospike');

const exp = Aerospike.exp;

const lists = Aerospike.lists;

const op = Aerospike.operations;

async function main() {

// start with a clean state

await truncateTestData();

// 1. The test data is populated with three randomly generated test scores ranging from 50 to 100

//    for student ids 1-20.

const ScoreRange = 50;

const writePolicy = new Aerospike.WritePolicy({ key: Aerospike.policy.key.SEND });

for (let i = 1; i <= 20; i++) {

    const key = new Aerospike.Key(NAMESPACE, SET_NAME, `id-${i}`);

    const tests = [

        50 + Math.floor(Math.random() * ScoreRange),

        50 + Math.floor(Math.random() * ScoreRange),

        50 + Math.floor(Math.random() * ScoreRange),

    ];

    await client.put(key, { id: i, tests }, {}, writePolicy);

}

console.log('Test data populated.');

// 2. The data is updated by writing two additional bins: "class" which represents the teacher's input (0-10)

//    based on class participation, and "grade" which is computed by adding "classwork" to average of test scores,

//    and using this formula: 50-70 -> C, 65==70-85 -> B, 85+ -> A.

// define the expressions for average test score, total score, grade, min test score, and max test score

const avgScoreExpr = exp.let(

    ...exp.def('testsBin', exp.binList('tests')),

    exp.div(

        exp.add(

            exp.lists.getByIndex(exp.var('testsBin'), exp.int(0), exp.type.INT, lists.returnType.VALUE),

            exp.lists.getByIndex(exp.var('testsBin'), exp.int(1), exp.type.INT, lists.returnType.VALUE),

            exp.lists.getByIndex(exp.var('testsBin'), exp.int(2), exp.type.INT, lists.returnType.VALUE)),

        exp.int(3)));

const totalScoreExpr = exp.let(

    ...exp.def('classBin', exp.binInt('class')),

    exp.add(avgScoreExpr, exp.var('classBin')));

const gradeExpr = exp.let(

    ...exp.def('total', totalScoreExpr),

    exp.cond(

        exp.lt(exp.var('total'), exp.int(70)), exp.str('C'),

        exp.le(exp.var('total'), exp.int(85)), exp.str('B'),

        exp.gt(exp.var('total'), exp.int(85)), exp.str('A'),

        exp.unknown()));

const minExpr = exp.lists.getByRank(exp.binList('tests'), exp.int(0), exp.type.INT, lists.returnType.VALUE);   // min

const maxExpr = exp.lists.getByRank(exp.binList('tests'), exp.int(-1), exp.type.INT, lists.returnType.VALUE);  // max

// update class bin with a random 1-10, and the grade bin using gradeExpr

for (let i = 1; i <= 20; i++) {

    const key = new Aerospike.Key(NAMESPACE, SET_NAME, `id-${i}`);

    const classwork = Math.floor((Math.random() * ScoreRange) / 5);

    // write gradeExpr to the bin "grade"

    const gradeExpOp = exp.operations.write('grade', gradeExpr, exp.expWriteFlags.DEFAULT);

    await client.operate(key, [op.write('class', classwork), gradeExpOp], {}, writePolicy);

}

console.log('Updated class participation and computed grades.');

// run a report for all students with id, grade, total, min, max, avg

// using total, min, max, and avg expressions defined above

const ops = [

    op.read('id'),

    op.read('grade'),

    exp.operations.read('total', totalScoreExpr, exp.expReadFlags.DEFAULT),

    exp.operations.read('testMin', minExpr, exp.expReadFlags.DEFAULT),

    exp.operations.read('testMax', maxExpr, exp.expReadFlags.DEFAULT),

    exp.operations.read('testAvg', avgScoreExpr, exp.expReadFlags.DEFAULT),

];

for (let i = 1; i <= 20; i++) {

    const key = new Aerospike.Key(NAMESPACE, SET_NAME, `id-${i}`);

    const record = await client.operate(key, ops);

    console.log(`key=${record.key.key} bins=${JSON.stringify(record.bins)}`);

}

}

main().catch(console.error);
```

::: note
Output values depend on randomly generated test data. JavaScript’s `Math.random()` is unseeded, so the printed grades, totals, and test scores will differ from the Java output above (and from run to run), though the shape of the result is the same.
:::

This assumes the same setup as the Java example above, translated to the Rust client: `Client::new(&ClientPolicy::default(), &"127.0.0.1:3000".to_string()).await?`, with `NAMESPACE = "test"` and `SET = "expressions"`. This example requires the `rand` crate (`cargo add rand`).

```rust
use aerospike::expressions::lists::{get_by_index, get_by_rank};

use aerospike::expressions::{cond, def, exp_let, gt, int_bin, int_val, le, list_bin, lt, num_add, num_div, string_val, unknown, var, ExpType};

use aerospike::operations::exp::{read_exp, write_exp, ExpReadFlags, ExpWriteFlags};

use aerospike::{as_bin, as_key, as_list, operations, ListReturnType, WritePolicy};

use rand::{rngs::StdRng, Rng, SeedableRng};

// start with a clean state

truncate_test_data(&client).await;

// 1. The test data is populated with three randomly generated test scores ranging from 50 to 100

//    for student ids 1-20.

let mut rng = StdRng::seed_from_u64(1);

const SCORE_RANGE: i64 = 50;

let mut wpolicy = WritePolicy::default();

wpolicy.send_key = true;

for i in 1..=20 {

    let key = as_key!(NAMESPACE, SET, format!("id-{i}"));

    let (t0, t1, t2) = (

        50 + rng.gen_range(0..SCORE_RANGE),

        50 + rng.gen_range(0..SCORE_RANGE),

        50 + rng.gen_range(0..SCORE_RANGE),

    );

    let bins = vec![as_bin!("id", i), as_bin!("tests", as_list!(t0, t1, t2))];

    client.put(&wpolicy, &key, &bins).await?;

}

println!("Test data populated.");

// 2. The data is updated by writing two additional bins: "class" which represents the teacher's input (0-10)

//    based on class participation, and "grade" which is computed by adding "classwork" to average of test scores,

//    and using this formula: 50-70 -> C, 65==70-85 -> B, 85+ -> A.

// define the expressions for average test score, total score, grade, min test score, and max test score

let avg_score_exp = exp_let(vec![

    def("testsBin".to_string(), list_bin("tests".to_string())),

    num_div(vec![

        num_add(vec![

            get_by_index(ListReturnType::Values, ExpType::INT, int_val(0), var("testsBin".to_string()), &[]),

            get_by_index(ListReturnType::Values, ExpType::INT, int_val(1), var("testsBin".to_string()), &[]),

            get_by_index(ListReturnType::Values, ExpType::INT, int_val(2), var("testsBin".to_string()), &[]),

        ]),

        int_val(3),

    ]),

]);

let total_score_exp = exp_let(vec![

    def("classBin".to_string(), int_bin("class".to_string())),

    num_add(vec![avg_score_exp.clone(), var("classBin".to_string())]),

]);

let grade_exp = exp_let(vec![

    def("total".to_string(), total_score_exp.clone()),

    cond(vec![

        lt(var("total".to_string()), int_val(70)), string_val("C".to_string()),

        le(var("total".to_string()), int_val(85)), string_val("B".to_string()),

        gt(var("total".to_string()), int_val(85)), string_val("A".to_string()),

        unknown(),

    ]),

]);

let min_exp = get_by_rank(ListReturnType::Values, ExpType::INT, int_val(0), list_bin("tests".to_string()), &[]);   // min

let max_exp = get_by_rank(ListReturnType::Values, ExpType::INT, int_val(-1), list_bin("tests".to_string()), &[]);  // max

// update class bin with a random 1-10, and the grade bin using grade_exp

for i in 1..=20 {

    let key = as_key!(NAMESPACE, SET, format!("id-{i}"));

    let classwork = rng.gen_range(0..SCORE_RANGE) / 5;

    let class_bin = as_bin!("class", classwork);

    // write grade_exp to the bin "grade"

    let ops = [

        operations::scalar::put(&class_bin),

        write_exp("grade", grade_exp.clone(), ExpWriteFlags::Default),

    ];

    client.operate(&wpolicy, &key, &ops).await?;

}

println!("Updated class participation and computed grades.");

// run a report for all students with id, grade, total, min, max, avg

// using total, min, max, and avg expressions defined above

let ops = [

    operations::scalar::get_bin("id"),

    operations::scalar::get_bin("grade"),

    read_exp("total", total_score_exp.clone(), ExpReadFlags::Default),

    read_exp("testMin", min_exp.clone(), ExpReadFlags::Default),

    read_exp("testMax", max_exp.clone(), ExpReadFlags::Default),

    read_exp("testAvg", avg_score_exp.clone(), ExpReadFlags::Default),

];

for i in 1..=20 {

    let key = as_key!(NAMESPACE, SET, format!("id-{i}"));

    let record = client.operate(&WritePolicy::default(), &key, &ops).await?;

    println!("key={:?} bins={:?}", key.user_key, record.bins);

}
```

::: note
Output values depend on randomly generated test data. Rust’s `rand` crate produces a different sequence than Python’s `random`, Java’s `java.util.Random`, Go’s `math/rand`, or .NET’s `Random` even from the same seed, so the printed grades, totals, and test scores will differ from the Java output above, though the shape of the result is the same.
:::

You may view the state of the database and ensure correctness of the output by running the following command in the terminal tab:

`aql -c "select * from test.expressions"`

## AEL text equivalent

The Developer SDK expresses the same computation as [AEL](https://aerospike.com/docs/develop/client/sdk/concepts/ael/reference) text, using `let ... then` to bind the three test scores, and `when` in place of `cond` for the grade lookup. The average score (`testAvg`) is:

```text
let (t0 = $.tests.[0], t1 = $.tests.[1], t2 = $.tests.[2])

then ((${t0} + ${t1} + ${t2}) / 3)
```

The total score (`testAvg` plus `class`) extends that same binding:

```text
let (t0 = $.tests.[0], t1 = $.tests.[1], t2 = $.tests.[2],

     avg = ((${t0} + ${t1} + ${t2}) / 3), classBin = $.class)

then (${avg} + ${classBin})
```

The “grade” write expression reuses `total` inside a `when`:

```text
let (t0 = $.tests.[0], t1 = $.tests.[1], t2 = $.tests.[2],

     avg = ((${t0} + ${t1} + ${t2}) / 3), classBin = $.class,

     total = ${avg} + ${classBin})

then (

    when (

        ${total} < 70 => 'C',

        ${total} <= 85 => 'B',

        ${total} > 85 => 'A',

        default => unknown))
```

::: note
AEL’s `/` is type-directed, like `Exp.div`: dividing two `INT` operands always returns `INT`, truncating toward zero the same way `Exp.div` does, regardless of whether the result is a whole number. AEL only returns `FLOAT` when both operands are `FLOAT`s (for example, dividing by `3.0`). All operands must be the same type, either `INT` or `FLOAT`. Because `t0`, `t1`, and `t2` are all `INT` here, `(${t0} + ${t1} + ${t2}) / 3` is already `INT` and needs no `.toInt()` cast.
:::

The min and max test scores use the same rank selectors as `ListExp.getByRank` above: `$.tests.[#0]:INT` (smallest) and `$.tests.[#-1]:INT` (largest). The `:INT` pin is required on the tail of a `selectFrom` target because a read has to resolve its own result type, and an unpinned rank selector gives the compiler nothing to infer from. Writing the grade and reading the report with the Developer SDK:

::: note
These AEL fragments are not standalone runnable examples. `session` is a Developer SDK `Session` created separately (see the [AEL overview](https://aerospike.com/docs/develop/client/sdk/concepts/ael)), `students` is the `DataSet` defined at the top of each tab below, and `i` is the loop variable from the same `1..=20` student-id range used in the `Exp.*` examples above.
:::

-   [Java](#tab-panel-5655)
-   [Python](#tab-panel-5656)
-   [Go](#tab-panel-5657)
-   [C#](#tab-panel-5658)
-   [Node.js](#tab-panel-5659)
-   [Rust](#tab-panel-5660)

```java
DataSet students = DataSet.of(NAMESPACE, SET_NAME);

final String TESTS = "t0 = $.tests.[0], t1 = $.tests.[1], t2 = $.tests.[2]";

final String SUM = "(${t0} + ${t1} + ${t2})";

final String AVG = "(" + SUM + " / 3)";

// Individual expressions (each is a complete AEL string)

final String avg = "let (" + TESTS + ") then " + AVG;

final String total =

    "let (" + TESTS + ", avg = " + AVG + ", classBin = $.class) "

  + "then (${avg} + ${classBin})";

final String grade =

    "let (" + TESTS + ", avg = " + AVG + ", classBin = $.class, total = ${avg} + ${classBin}) "

  + "then (when (${total} < 70 => 'C', ${total} <= 85 => 'B', ${total} > 85 => 'A', default => unknown))";

session.upsert(students.id(String.valueOf(i)))

    .bin("grade").upsertFrom(grade)

    .execute();

Record record = session.query(students.id(String.valueOf(i)))

    .bin("total").selectFrom(total)

    .bin("testMin").selectFrom("$.tests.[#0]:INT")

    .bin("testMax").selectFrom("$.tests.[#-1]:INT")

    .bin("testAvg").selectFrom(avg)

    .execute()

    .getFirstRecord();
```

```python
students = DataSet.of(NAMESPACE, SET_NAME)

avg = ("let (t0 = $.tests.[0], t1 = $.tests.[1], t2 = $.tests.[2]) "

       "then (${t0} + ${t1} + ${t2}) / 3")

total = ("let (t0 = $.tests.[0], t1 = $.tests.[1], t2 = $.tests.[2], "

         "avg = (${t0} + ${t1} + ${t2}) / 3, classBin = $.class) "

         "then (${avg} + ${classBin})")

grade = ("let (t0 = $.tests.[0], t1 = $.tests.[1], t2 = $.tests.[2], "

         "avg = (${t0} + ${t1} + ${t2}) / 3, classBin = $.class, "

         "total = ${avg} + ${classBin}) "

         "then (when (${total} < 70 => 'C', ${total} <= 85 => 'B', "

         "${total} > 85 => 'A', default => unknown))")

await (

    session.upsert(students.id(str(i)))

    .bin("grade").upsert_from(grade)

    .execute()

)

stream = await (

    session.query(students.id(str(i)))

    .bin("total").select_from(total)

    .bin("testMin").select_from("$.tests.[#0]:INT")

    .bin("testMax").select_from("$.tests.[#-1]:INT")

    .bin("testAvg").select_from(avg)

    .execute()

)

record = await stream.first_or_raise()
```

::: note
AEL text authoring is available for the Developer SDK (Java and Python) only. Go continues to use the `Exp*` builder shown above.
:::

::: note
AEL text authoring is available for the Developer SDK (Java and Python) only. C# continues to use the `Exp` builder shown above.
:::

::: note
AEL text authoring is available for the Developer SDK (Java and Python) only. Node.js continues to use the `exp` builder shown above.
:::

::: note
AEL text authoring is available for the Developer SDK (Java and Python) only. Rust continues to use the `aerospike::expressions` builder shown above.
:::

## Next

Continue to [Usage notes and next steps](https://aerospike.com/docs/develop/tutorials/operations/expressions/usage-notes).