Chapter 5: Types and Operators

Bosque supports a multiple type system that allows developers to have sufficient flexibility to develop their programs through nominal structured or combined types. To take advantage of the language, it is essential to become familiar with the different supported types and operators that Bosque provides.

In this chapter, we will explore each of the type systems, review the core types, and discover some special types and the main operators that Bosque offers us. For this chapter, we will cover the following topics:

  • Bosque type system
  • Nominal type system
  • Structural type system
  • Typed strings
  • Operators

By the end of this chapter, you will be able to understand the Bosque type systems, as well as identify and use each operator available to process information from different types.

Technical requirements

The following are the requirements for this chapter:

  • It is necessary to have Bosque installed successfully. The complete process to perform this has been explained in Chapter 2, Configuring a Bosque Environment.
  • A text editor of your choice.

Bosque type system

Bosque provides more than one single type system. It gives us more flexibility and provides us with more options for building our programs. In this section, we will learn more about each type system so that we can understand how each type adds value to the software construction process.

Type systems in a programming language first appeared for the purpose of allowing the programmer to choose how data would be stored in memory.

One of its first implementations can be found in FORTRAN, which allowed us to define whether a variable would store an integer or floating-point data using some specific characters at the beginning of the variable identifiers' names.

Later, type systems were also used to prevent errors in execution time through type checking, which was implemented for the first time in ALGOL 60.

Nowadays, type systems also provide abstraction mechanisms that provide simplified interfaces to interact with data structures, thereby allowing the programmer to have a broader range of tools when solving problems.

The Bosque type system provides three different perspectives so that the programmer can have greater flexibility when building their applications, which are as follows:

  • Nominal type system: The most familiar type system for programmers with experience in object-oriented languages.
  • Structural type system: A type system focused on data structures.
  • Combinational type system: A type system oriented to Boolean algebra operations between types.

Let's look at these type systems in the following subsections in a little more detail.

Nominal type system

The nominal type system provides a familiar interface for programmers who come from object-oriented languages, thanks to the implementation of concepts and entities, which could be interpreted, saving the differences, with abstract classes and classes.

We can create concepts to define abstract types, which allow us to implement inheritance, constants, or static functions. Just like abstract classes, concepts are never instantiated and are declarative or descriptive.

We can define a concept using the concept keyword as follows:

concept Foo {

     // Something

}

A concept in Bosque can define and implement static functions, member fields, constants, and methods. Let's see a comparison between JavaScript and Bosque implementations. We see the following snippet in which a simple class with a constructor is declared:

// JavaScript

class Foo {

      constructor(x) {

           this.x = x;

     }

}

On the other hand, we can see the following snippet written in Bosque, which has a similar implementation using the language's own characteristics, such as concepts and fields:

//Bosque

concept Foo {

     field x: Int;

}

Furthermore, we could use the entity declaration to create types that can instantiate concepts. These inherit the methods, fields, and constants from the concept.

To create an entity, we will use the entity keyword as follows:

entity Bar provides Foo {

     //

}

Let's see an example involving more extensive code. In the following snippet, we have a basic example of inheritance in JavaScript:

// JavaScript

class Foo {

      constructor(x) {

          this.x = x;

      }

}

class Bar extends Foo {

     constructor(x) {

     super(x)

     }

}

var baz = new Bar(1)

As we can see in the following snippet, the nominal type system allows us to implement a very similar solution using Bosque through concepts and entities:

// Bosque

concept Foo {

     field x: Int;

}

entity Bar provides Foo { }

let baz = Bar@{x=1}

Do not worry if you don't understand the previous code. We will learn more about these topics later. The important thing is to recognize that Bosque provides us with similar tools, such as languages with object-oriented programming support. As we have seen, this type system allows us to adopt known practices and patterns in object-oriented programming languages. Next, we will see another Bosque type system that will shape our most basic data structures.

Structural type system

The structural type system provides two main types, tuples and records, which are the most basic data structures. They are commonly used to define the arguments received by a function. Let's look at these types more closely in the following subsections.

Tuples

A tuple is a list of items providing a type that can be marked as optional. Each tuple is a subtype of the nominal type Tuple..

Let's look at some examples of tuple declarations:

[ Int ]           // Tuple of an Integer value

[ Int, Bool ]     // Tuple of an Integer and Boolean value

[ Int, ?:Bool ]   // Tuple of an Integer and optional Boolean value

But we could also use a literal constructor syntax, as follows:

[]               // Empty Tuple

[1, 2]           // Tuple of Integer values

[1, "a"]         // Tuple of an Integer and String value

[1, foo()]       // Tuple of an Integer value and the result of foo()

Records

We could understand a Record as a map of named elements. Some of these could be marked as optional.

Let's now look at some examples of Record declarations:

// Record with an Integer value with an identifier called x

{ x: Int }

// Record with two required values y and z, both Integer

{ y: Int, z: Int }

// Record with an Integer and an optional Boolean value

{ x: Int, b?: Bool}    

However, as with tuples, we can also use a literal constructor syntax, as follows:

// Empty record

{ }

// A record with an Integer value identified by x

{ x = 1 }

// A record with an integer and Boolean values

{ x = 1, y = true }

Core types

Bosque provides some basic types established in the definition of the language itself, commonly called primitive types. Let's see what they are.

Int

The Int type is used to represent positive and negative whole numbers whose values can be in the range from -9007199254740991 to 9007199254740991.

Let's see an example of a declaration and assignment of a Int type identifier:

var n: Int;    

n = 53;

Here is a tiny subset of useful methods available on Int type:

Figure 5.1 – Int type available methods

Figure 5.1 – Int type available methods

Bool

A Bool type can only store a Boolean value (true or false) as a bit representation – 1, 0.

Let's now see an example of the declaration and assignment of a Bool type identifier:

var flag: Bool;

flag = true;

According to the Bool implementation, we have the following useful methods:

Figure 5.2 – Bool type available methods

Figure 5.2 – Bool type available methods

String

A String type represents a string of characters commonly used to store text.

Let's now see an example of the declaration and assignment of a String type identifier:

var greeting: String;

greeting = "Hello world";

According to the String implementation, we have the following useful methods:

Float64

A Float64 represents a number in floating-point notation, used to store numbers with a decimal point so as not to lose precision in the case of mathematical operations.

Let's now see an example of the declaration and assignment of a Float64 type identifier:

var f: Float64;

f = 0.54f

According to the Float64 implementation, we have the following useful methods:

The Float64 Bosque type allows us to use a single type to represent existing types in other languages, such as Float, Decimal, or Double, providing the maximum possible precision with this type.

Typed strings

In addition to the primitive String type, Bosque provides two additional types to broaden the possibilities through extra features such as metadata or supporting validation through regular expressions – SafeString<T> and StringOf<T>. Let's learn more about these in the following subsections.

SafeString<T>

The SafeString <T> type is defined by a parameter representing a validation pattern for its content through a regular expression. This feature can be useful for simplifying string formatting or for performing content validation.

Let's consider an example program to convert USD to EUR that are provided as strings with the currency symbol.

Important note

As of this writing, the methods of the String and Regex entities presented in the next example are not fully implemented yet. Therefore the code will not compile properly. Keep this in mind while analyzing it.

Have a look at the following practical example:

typedef CurrencyFormat = /(d)+(USD|EUR)/; // Currency valid format

function convertToUSD (input: SafeString<CurrencyFormat>): Float64 {

    let strInput = input.string();

    let regexMatch = /(d)+/.match(strInput);

    let dval = Float64::parse(strInput.substring(regexMatch.index, regexMatch.index + regexMatch.length));

    if(strInput.endsWith("USD")) {

        return dval;

    }

    else {

        return convertEURToUSD(dval);

    }

}

function convertEURToUSD (euros: Float64): Float64 {

    // return euros * 1.18f;

}

In the previous code, we can identify a function called convertToUSD that aims to return an amount in dollars and carry out the conversion if the amount entered is expressed in euros.

In this function, we can see a parameter called input. This is typed as SafeString <T>, which means that if we call the function with an amount expressed with another currency identifier, for example, 100 MXN, this will result in a type error.

Let's see how it would be used with the help of the following example:

var amount = CurrencyFormat'100EUR';

return convertToUSD(amount); // 118.0

In the previous code, we see how a string with a valid format can be processed by complying with the format expected by SafeString, where finally this process returns a Float64 value that represents the conversion of the numerical part of the string.

StringOf<T>

On the other hand, we also have a more flexible option, the StringOf<T> type, which can be defined through a parameter that can be of any type, with the only condition that it implements the Parsable concept.

This extra flexibility also means that we must manually specify the parsing process by overwriting the T::tryParse method.

Let's look at a practical example:

entity PhoneNumber provides Parsable {

    field number: String;

    hidden static parseNumber(str: String): Result<String, String> {

        if (str.length() <= 9) {

            return ok(str);

        } else {

            return err("Invalid Phone Number");

        }

    }

    override static tryParse(str: String): Result<PhoneNumber, String> {

        let result = PhoneNumber::parseNumber(str);

        if(result.isErr()) {

            return Result<Any, String>::err(err(result.failure());

        }

        else {

            let phone = PhoneNumber@{number=result.result()};

            return Result ok(phone);

        }

    }

}

function sendSMSNotification(phone: StringOf<PhoneNumber>: String {

     // Send the notification

return String::concat("Send to ", phone.string());

}

In this example, we observe a function that aims to obtain a valid phone number from a string to send an SMS notification.

For this purpose, an entity that implements the Parsable concept is generated. The tryParse method is overwritten to implement a more complex method of validating phone numbers in an imperative way instead of just a simple descriptive method through a validation based on regular expressions, opening a range of possibilities as regards custom parsing implementations. The types exposed in this chapter, in addition to improving clarity, enable identification of the text validation parameters' restrictions in a function, thereby enhancing the testability of our applications. Next, we will explain the operators that Bosque provides to carry out between the diverse types available.

Operators

Operators are an intrinsic part of the definition of a programming language. They establish the way in which one or more operands are resolved by an operation expressed through a predefined symbol in the language.

To have a clearer idea of how an operator works, let's remember how we use arithmetic operators in real life:

4 + 5

As we know, if we find the + symbol located between two numbers, this indicates that when executing the operation, both operands 4 and 5 will be solved by an addition operation, returning 9.

We call this operation a binary arithmetic operation since it involves two operators and is defined as employing an arithmetic calculation. Still in a language, we have a set of several diverse types of operators.

In the next subsections, we will explore each available operator in Bosque.

Unary operators

Bosque supports three types of unary operations that are defined by a symbol at the beginning of an expression. The following table shows some of these expressions and its uses:

Let's look at some examples of these:

!true           // Returns false

!false          // Returns true

!none           // Returns true

!"hello"        // Type error

!5              // Type error

+9              // Return 9

-5              // Return -5

In summary, as in most known languages, a unary operator is represented by a symbol at the beginning of an expression and receives a single parameter located on the right. The common operations are the negation and the explicit indicator for a number's negativity or positivity.

Binary operators

Bosque provides a set of arithmetic operators that can be used when employing the desired operation symbol between two operands or by calling the static methods of Math and passing the operands as arguments.

The following table lists some of these operators:

Let's see some examples of these operations in the following code:

6 + 8      // 15

9 – 5      // 4

3 * 9      // 27

6 / 2      // 3

As we said before, these expressions can also be performed using Math methods, as we can see here:

Math::sum(6, 8)       // 15

Math::sub(9, 5)       // 4

Math::mult(3, 9)      // 27

Math::div(6, 2)       // 3

As we can observe, arithmetic binary operations in Bosque are performed in the same way as in other languages.

Logic operators

Bosque provides the basic logical operators && (AND) and || (OR), which are valid only for Bool type operands. Additionally, the ==> operator is included, which is only false if the first operator is true and the second one is false.

Let's see some examples:

OR

true || false         // true

false || false        // false

AND

true && true          // true

false && true         // false

false && false        // false

Material conditional (→)

true ==> false        // false

true ==> true         // true

false ==> true        // true

false ==> false       // true

Order comparison operators

Bosque has operators designed to carry out order operations between two values, that is, to identify whether one is greater or less than another. In the following table, the operands and its use cases are listed.

Let's see some examples:

1 < 5           // true

2 > 0           // true

13 <= 8         // false

9 >= 3          // true

6 > "3"         // error

We have intentionally omitted the equality operator, which you will see in the next heading.

Equality comparison operators

To evaluate equality between two operands in Bosque, you can use the antagonistic symbols == (equality) and != (inequality), although it is also possible to use the static KeyType::equal(a: KeyType, b: KeyType) method.

An expression containing the == operator will always be evaluated from left to right.

Let's see some examples:

5 == 5          // true

"7" == 7        // error

"9" != ""       // false

false == none   // false

On the other hand, Bosque does not support reference equality, but it is possible to define an identification key to allow equality operations between more complex types. This identifier can be composite, allowing more than one field within its definition.

Let's see how they work:

identifier UniqueID = Int;

entity Resource {

     field id: UniqueID;

     // ...

}

let x = Resource@{1};

let y = Resource@{2};

x == x     // true

y == x     // false

In the previous code, we can see a numeric identifier declaration that will become part of the Resource entity. In this way, we can compare different instances through an equality operation.

Now, let's see how a compound identifier works:

composite identifier CompoundKey = { id: Int, version: String };

entity Resource {

     field idkey: CompoundKey;

     // ...

}

let x = Resource@{ idkey: {id: 10, version: "beta"} };

let y = Resource@{ idkey: {id: 10, version: "alpha"} };

x == x     // true

y == x     // false

In the previous code, we can see that the identifier is composed of two fields, id and version, which will be evaluated during the equality comparison process.

Select operators

The selection operator evaluates a Boolean condition and returns the value to the left of the : symbol if the condition is true, and the value to the right if it is false. The use of this operator facilitates improved code readability through a ternary operation. The none value is considered false.

Let's see some examples:

true ?  "yes" : "no"      // yes

false ? "yes" : "no"       // no

none ? "yes" : "no"        // no

"" ? "yes" : "no"          // error

We have now learned to identify the different type systems and operators that Bosque provides.

Summary

Bosque adopts many of the types and operators that we already know from our experience in other languages, simplifying their adoption. However, it is important to consider the deterministic nature of language, so it is important to understand how Bosque implements them and how they should be used to obtain the expected results while respecting the language's paradigm.

In this chapter, we have learned that the nominal type system is advantageous during the learning process since it allows the reasoning of the well-known object-oriented programming to be imitated.

Still, it is important to try to take full advantage of the structures that the language offers, such as collections and sets, and that not every implementation based on object-oriented programming is necessarily idiomatic or will efficiently take advantage of the paradigm that implements the language.

On the other hand, we also saw that in addition to the basic arithmetic, logic, or comparison operators, Bosque provides some operators that allow us to improve our code's readability as selection operators or none-coalescing operations.

And finally, we learned how Bosque solves the equality evaluation problem by implementing identifier fields and eliminating the need to implement referential equality.

Now we are ready to use these in our programs and continue to familiarize ourselves with the language. In the next chapter, we will explore the syntax of this language with a view to building more complex sentences.

Questions

  1. What is a type?
  2. How many type systems does Bosque support?
  3. Does Bosque implement reference equality?

Further reading

  • Jang, H., Dong, L., Xingyuan, Z., and Xiren, X. (2001). Type system in programming languages. Journal of Computer Science and Technology, 286–292
  • Pierce, B. C. (2002). Types and Programming Languages. The MIT Press.
  • Cardelli, L. (1996). Type systems. ACM Computing Surveys, 263-264.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.42.168