--- name: Ballerina contributors: - ["Anjana Fernando", "https://github.com/lafernando"] filename: learn_ballerina.bal --- [Ballerina](https://ballerina.io/) is a statically-typed programming language for making development for the cloud an enjoyable experience. ```java // Single-line comment // Import modules into the current source file import ballerina/io; import ballerina/time; import ballerina/http; import ballerinax/java.jdbc; import ballerina/lang.'int as ints; import ballerinax/awslambda; // Module alias "af" used in code in place of the full module name import ballerinax/azure.functions as af; http:Client clientEP = new ("https://freegeoip.app/"); jdbc:Client accountsDB = new ({url: "jdbc:mysql://localhost:3306/AccountsDB", username: "test", password: "test"}); // A service is a first-class concept in Ballerina, and is one of the // entrypoints to a Ballerina program. // The Ballerina platform also provides support for easy deployment to // environments such as Kubernetes (https://ballerina.io/learn/deployment/kubernetes/). service geoservice on new http:Listener(8080) { @http:ResourceConfig { path: "/geoip/{ip}" } resource function geoip(http:Caller caller, http:Request request, string ip) returns @tainted error? { http:Response resp = check clientEP->get("/json/" + <@untainted>ip); check caller->respond(<@untainted> check resp.getTextPayload()); } } // Serverless Function-as-a-Service support with AWS Lambda. // The Ballerina compiler automatically generates the final deployment // artifact to be deployed. @awslambda:Function public function echo(awslambda:Context ctx, json input) returns json { return input; } @awslambda:Function public function notifyS3(awslambda:Context ctx, awslambda:S3Event event) returns json { return event.Records[0].s3.'object.key; } // Serverless Function-as-a-Service support with Azure Functions. // Similar to AWS Lambda, the compiler generates the deployment artifacts. @af:Function public function fromQueueToQueue(af:Context ctx, @af:QueueTrigger { queueName: "queue1" } string inMsg, @af:QueueOutput { queueName: "queue2" } af:StringOutputBinding outMsg) { outMsg.value = inMsg; } // A custom record type public type Person record { string id; // required field string name; int age?; // optional field string country = "N/A"; // default value }; @af:Function public function fromHttpTriggerCosmosDBInput( @af:HTTPTrigger { route: "c1/{country}" } af:HTTPRequest httpReq, @af:CosmosDBInput { connectionStringSetting: "CosmosDBConnection", databaseName: "db1", collectionName: "c1", sqlQuery: "select * from c1 where c1.country = {country}" } Person[] dbReq) returns @af:HTTPOutput string|error { return dbReq.toString(); } public function main() returns @tainted error? { int a = 10; // 64-bit signed integer float b = 1.56; // 64-bit IEEE 754-2008 binary floating point number string c = "hello"; // a unicode string boolean d = true; // true, false decimal e = 15.335; // decimal floating point number var f = 20; // type inference with 'var' - 'f' is an int int[] intArray = [1, 2, 3, 4, 5, 6]; int x = intArray.shift(); // similar to a dequeue operation x = intArray.pop(); // removes the last element intArray.push(10); // add to the end // Tuples - similar to a fixed length array with a distinct type for each slot [string, int] p1 = ["Jack", 1990]; [string, int] p2 = ["Tom", 1986]; io:println("Name: ", p1[0], " Birth Year: ", p1[1]); string name1; int birthYear1; [name1, birthYear1] = p1; // tuple destructuring var [name2, birthYear2] = p2; // declare and assign values in the same statement // If statements int ix = 10; if ix < 10 { io:println("value is less than 10"); } else if ix == 10 { io:println("value equals to 10"); } else { io:println("value is greater than 10"); } // Loops int count = 10; int i = 0; while i < 10 { io:println(i); } // Loop from 0 to count (inclusive) foreach var j in 0...count { io:println(j); } // Loop from 0 to count (non-inclusive) foreach var j in 0..{{name1}}{{birthYear1}}`; io:println(x1); // Access specific elements in the XML value io:println(x1/); // List all child items in the XML value io:println(x1/*); // Function invocations x = add(1, 2); io:println(multiply(2, 4)); // Invocation providing value for the defaultable parameter io:println(multiply(3, 4, true)); // Invocation with values to a rest parameter (multi-valued) io:println(addAll(1, 2, 3)); io:println(addAll(1, 2, 3, 4, 5)); // Function pointers (function (int, int) returns int) op1 = getOperation("add"); (function (int, int) returns int) op2 = getOperation("mod"); io:println(op1(5, 10)); io:println(op2(13, 10)); // Closures (function (int x) returns int) add5 = getAdder(5); (function (int x) returns int) add10 = getAdder(10); io:println(add5(10)); io:println(add10(10)); int[] numbers = [1, 2, 3, 4, 5, 6, 7, 8]; // Functional iteration int[] evenNumbers = numbers.filter(function (int x) returns boolean { return x % 2 == 0; }); // Union types - "input" is of type either string or byte[] string|byte[] uval = "XXX"; // A type test expression ("uval is string") can be used to check the // runtime type of a variable. if uval is string { // In the current scope, "uval" is a string value string data = "data:" + uval; } else { // Since the expression in the "if" statement ruled out that it's not a string, // the only type left is "byte[]"; so in the current scope, "uval" will always // be a "byte[]". int inputLength = uval.length(); } // Error handling string input = io:readln("Enter number: "); int|error result = ints:fromString(input); if result is int { io:println("Number: ", result); } else { io:println("Invalid number: ", input); } // A check expression can be used to directly return the error from // the current function if its subexpression evaluated to an error // value in the runtime. int number = check ints:fromString(input); // Concurrent execution using workers in a function doWorkers(); // Asynchronous execution with futures future f10 = start fib(10); var webresult = clientEP->get("/"); int fresult = wait f10; if webresult is http:Response { io:println(webresult.getTextPayload()); io:println(fresult); } // Mapping types map ageMap = {}; ageMap["Peter"] = 25; ageMap["John"] = 30; int? agePeter = ageMap["Peter"]; // int? is the union type int|() - int or nill if agePeter is int { io:println("Peter's age is ", agePeter); } else { io:println("Peter's age is not found"); } Person person1 = { id: "p1", name : "Anne", age: 28, country: "Sri Lanka" }; Scores score1 = { physics : 80, mathematics: 95 }; score1["chemistry"] = 75; io:println(score1["chemistry"]); Student student1 = { id: "s1", name: "Jack", age: 25, country: "Japan" }; student1.college = "Stanford"; string? jacksCollege = student1?.college; // optional field access if jacksCollege is string { io:println("Jack's college is ", jacksCollege); } // Due to the structural type system, "student1" can be assigned to "person2", // since the student1's structure is compatible with person2's, // where we can say, a "Student" is a "Person" as well. Person person2 = student1; map grades = {"Jack": 95, "Anne": 90, "John": 80, "Bill": 55}; Person px1 = {id: "px1", name: "Jack", age: 30, country: "Canada"}; Person px2 = {id: "px2", name: "John", age: 25}; Person px3 = {id: "px3", name: "Anne", age: 17, country: "UK"}; Person px4 = {id: "px4", name: "Bill", age: 15, country: "USA"}; Person[] persons = []; persons.push(px1); persons.push(px2); persons.push(px3); persons.push(px4); // Query expressions used to execute complex queries for list data Result[] results = from var person in persons let int lgrade = (grades[person.name] ?: 0) where lgrade > 75 let string targetCollege = "Stanford" select { name: person.name, college: targetCollege, grade: lgrade }; // Compile-time taint checking for handling untrusted data string s1 = "abc"; mySecureFunction(s1); // Explicitely make "s2" a tainted value. External input to a Ballerina // program such as command-line arguments and network input are by-default // marked as tainted data. string s2 = <@tainted> s1; // "s2x" is now a tainted value, since its value is derived using a // tainted value (s1). string s2x = s2 + "abc"; // The following line uncommented will result in a compilation error, // since we are passing a tainted value (s2x) to a function which // exepects an untainted value. // mySecureFunction(s2x); // Instantiating objects Employee emp1 = new("E0001", "Jack Smith", "Sales", 2009); io:println("The company service duration of ", emp1.name, " is ", emp1.serviceDuration()); // Supported operations can be executed in a transaction by enclosing the actions // in a "transaction" block. transaction { // Executes the below database operations in a single local transactions var r1 = accountsDB->update("UPDATE Employee SET balance = balance + ? WHERE id = ?", 5500.0, "ID001"); var r2 = accountsDB->update("UPDATE Employee SET balance = balance + ? WHERE id = ?", 5500.0, "ID001"); } } // An object is a behavioural type, which encapsulates both data and functionality. type Employee object { // Private fields are only visible within the object and its methods private string empId; // Public fields can be accessed by anyone public string name; public string department; // The default qualifier is a "protected" field, // which are accessible only within the module. int yearJoined; // The object initialization function; automatically called when an object is instantiated. public function __init(string empId, string name, string department, int yearJoined) { self.empId = empId; self.name = name; self.department = department; self.yearJoined = yearJoined; } // An object method public function serviceDuration() returns int { time:Time ct = time:currentTime(); return time:getYear(ct) - self.yearJoined; } }; // Student is a subtype of Person type Student record { string id; string name; int age; string college?; string country; }; type Scores record { int physics; int mathematics; }; type Result record { string name; string college; int grade; }; public function getOperation(string op) returns (function (int, int) returns int) { if op == "add" { return add; } else if op == "mod" { return function (int a, int b) returns int { // anonymous function return a % b; }; } else { return (x, y) => 0; // single expression anonymous no-op function } } // Two required parameters public function add(int a, int b) returns int { return a + b; } // 'log' is a defaultable parameter public function multiply(int a, int b, boolean log = false) returns int { if log { io:println("Multiplying ", a, " with ", b); } return a * b; } // 'numbers' is a rest parameter - it can have multiple values, // similar to an array. public function addAll(int... numbers) returns int { int result = 0; foreach int number in numbers { result += number; } return result; } public function getAdder(int n) returns (function (int x) returns int) { return function (int x) returns int { // returns closure return x + n; }; } function fib(int n) returns int { if n <= 2 { return 1; } else { return fib(n - 1) + fib(n - 2); } } // The code in worker blocks "w1" and "w2" are executed concurrency // when this function is invoked. The "wait" expressions waits for // the given workers to finish to retrieve their results. public function doWorkers() { worker w1 returns int { int j = 10; j -> w2; int b; b = <- w2; return b * b; } worker w2 returns int { int a; a = <- w1; a * 2 -> w1; return a + 2; } record {int w1; int w2;} x = wait {w1, w2}; io:println(x); } // A function which takes in only an untainted string value. public function mySecureFunction(@untainted string input) { io:println(input); } ``` ### Further Reading * [Ballerina by Example](https://ballerina.io/learn/by-example/) * [User Guide](https://ballerina.io/learn/installing-ballerina/) * [API Documentation](https://ballerina.io/learn/api-docs/ballerina/) * [Language Specification](https://ballerina.io/spec/)