Experimentation Stage

Operators

Lense supports operators with a special kind of operator overloading. There are two types of operators : intrinsic and definable.

Intrinsic operators are native to the language and their behavior cannot be redefined. On the other hand, the set of available operators for redefinition is limited ( we do not what symbolic noise ) but extended enough to be useful in a mathematical or engineering context. Definable operators are translated to method calls. Intrinsic operators are not translated to method calls.

Operands are never changed during these operations.

Symbols and Double Symbols

Symbols are gathered from different sources and most are familiar and available in other programming languages. For arithmetic operations we need unique symbols. Not always there are enough symbols available. For example, we can use the ^ symbol do exponentiation, but then we do not have a good symbol for the XOR operation, traditionally also denoted ^. So, in this cases where the same symbol may be used, we prefer the single symbol for the commutative operation and the double symbol for the non commutative operation. So we use ^ for XOR and ^^ for exponentiation.

Note
Other languages use ** for exponentiation. This makes parsing more difficult, and produces a rather confusing code when using multiplications together with exponentiation, so ^^ symbol was selected instead;

Lense, distinguishes "sum" - a commutative operation - and "concatenation" - a non commutative operation. So + denotes "sum" and ++ denotes "concatenation". Strings, and other objects are concatenated as

let greetings = "Hello, " ++ "world"
Note
Not all double symbols imply non-commutativity. For example, == is commutative. The double symbol rule applies to arithmetic operators that have the two forms. For example, & denotes commutative AND operation while , && denotes non commutative, short-circuit AND operation.

Intrinsic Operators

Intrinsic operators cannot be redefined and are specially handled by the compiler.

Table 1. Intrinsic Operators

Operator

Example

===

a === b

Determines if two objects have the same identity. Only objects of type Identifiable can use this operator

!==

a !== b

Determines if two objects have not the same identity. Only objects of type Identifiable can use this operator

+ (infix)

+a

Does nothing. An infix + signed is ignored after parsing. It does not turn a negative value into a positive value.

&&

a && b

a and b must be Boolean. Performs an AND logic operation on the operands but only if a is true. Otherwise simply return false. Because of the short-circuit behaviour this is not a commutative operator.

||

a || b

a and b must be Boolean. Performs an OR logic operation on the operands but only if a is false. Otherwise simply return true.Because of the short-circuit behaviour this is not a commutative operator.

! (infix)

!a

a must be Boolean. Returns the logic complement of a.

interval operators

|[2, 5)|

Interval between 2 and 5, including 2 and excluding 5.

Interval operators allow to include of exclude the start and ending values

|[

Interval start, including the value

|(

Interval start, not including the value

]|

Interval end, including the value

)|

Interval end, not including the value

Ternary operators

Ternary Select operator

This operator test for the first term to be true. In the positive case returns the second term. Otherwise returns the third.

let c = (a == b) ? 1 : 4;
Ternary Comparison operator

This operator compares the second term with the other ones according to the comparison operators use in between them an returns true if both sides are true

let isTeenager =  13 <= x <= 19;

This operator is equivalent to

let isTeenager =  13 <= x && x <= 19

but, we do not need to use the && operator nor type the variable x twice. Also this operator is equivalent to

let isTeenager = x in |[ 13 , 19 ]|;

but we do not need to create and interval object to test x against it.

Definable Operators

Lense supports operator overloading by means of assigning an operator to a method. For example,

  let a = 2 + 3;

transforms to the plus method

  let a = 2.plus(3);
Note
You can use the plus method directly if you which, operators only provide a more fluent form
  // with operators
  x =  (-b + (b^^2 - 4 * a * c ) ^^ 0.5) / (2 * a);

  // without operators

  x =  (b.symmetric().plus(
			b.raiseTo(2).minus(4.multiply(a).multiply(c))
		)).raiseTo(0.5)
		.over(2.multiply(a));

Since operators are methods, this means operators are polymorphic and that the resulting type depends on the original types. For example, summing a Natural with a Natural results in a Natural, but summing a Natural with an Integer results in an Integer, since the argument can be negative. So Natural, for example, defines two methods called plus that correspond with the '+' operator;

   public plus(other : Natural): Natural;
   public plus(other : Integer): Integer;

The compiler will use the most specific one given the type of the argument.

Here is a list of all supported operators and their equivalent methods

Table 2. Definable Operators

Operator

Example

Equivalent method call

Commutative

Description

==

a == b

a.equals(b)

Yes

Determines if two instances are equal.

!=

a != b

!a.equals(b)

Yes

Determines if two instances are not equal.

+

a + b

a.plus(b)

Yes

Sums two values and returns a third value. If the result overloads the current representation, the result is a promoted representation

&+

a &+ b

a.wrapPlus(b)

Yes

Sums two values and returns a third value. If the result overloads the current representation, the value wrap around to a negative number

++

a ++ b

a.concat(b)

No

Concatenates two values and returns a third value.

-

a - b

a.minus(b)

No

Subtracts two values and returns a third value. If the result overloads the current representation, the result is a promoted representation

&-

a &- b

a.wrapMinus(b)

No

Subtracts two values and returns a third value. If the result overloads the current representation, the value wrap around to a negative number

*

a * b

a.multiply(b)

Yes

Multiplies the two values and returns in a third value. If the result overloads the current representation, the result is a promoted representation

&*

a &* b

a.wrapMulitply(b)

Yes

Multiplies the two values and returns in a third value.If the result overloads the current representation, the value wrap around to a negative number

^^

a ^^ b

a.raiseTo(b)

No

Raises the base - the first operand - to the power of the exponent - the second operand.

/

a / b

a.divide(b)

No

Divides the two values and returns a third value.

\

a \ b

a.wholeDivide(b)

No

Performs whole division the two values and returns a third value. The operand values are not changed in any way.

- (infix)

-a

a.symmetric()

n/a

Returns the symmetric value. Keep in mind the type needs not be closed for subtraction. For Natural`s, for example the symmetric value is an `Integer.

~ (infix)

~a

a.complement()

n/a

Returns the complement of the value. For Binary values it is equivalent to flipping all bits. For Complex numbers is represents the conjugate so that ~(a + ib) == a - ib

%

a % b

a.remainder(b)

No

Divides the two values and returns the remainder of integer division. Note that it always should be true that a == a \ b + a % b

&

a & b

a.and(b)

Yes

Injucts the two values and returns a third value. For binary forms, this implements a bitwise AND. For sets this implements intersection

|

a | b

a.or(b)

Yes

Dijuncts the two values and returns a third value. For binary forms, this implements a bitwise OR . For sets this implements union

^

a ^ b

a.xor(b)

Yes

Exclusively Dijuncts the two values and returns a third value. For binary forms, this implements a bitwise XOR

<⇒

a <⇒ b

a.compare(b)

No

Compared the order of the values of a and b. Returns Comparison.equal, Comparison.greater or Comparison.smaller if , respectively, a = b, a > b and a < b. The operand values are not changed in any way.

>

a > b

a.compare(b).isGreater()

No

Returns true if a is great than b, false otherwise.

>=

a >= b

a.!compare(b).isSmaller()

No

Returns true if a is great or equals to b, false otherwise.

<

a < b

a.compare(b).isSmaller()

No

Returns true if a is less than b, false otherwise.

a ⇐ b

a.!compare(b).isGreater()

No

Returns true if a is less or equal to b, false otherwise.

..

a .. b

a.upTo(b)

No

Returns a Progression that starts at a and ends at b, and contains b.

..<

a ..< b

a.upToExclude(b)

No

Returns a Progression that starts at a and ends at b, but not contains b.

>>

a >> n

a.rightShiftBy(n)

No

The arithmetic right shift operator returns a value equivalent to the original with bits moved to the right n times. This is equivalent to division by 2 n times for positive numeric values.

<<

a << n

a.leftShiftBy(n)

No

The arithmetic left shift operator returns a value equivalent to the original with bits moved to the left n times. This is equivalent to multiplication by 2 n times for positive numeric values.

empty space

a b

a.juxtapose(b)

No

This is an operator with no symbol that means the two operands are simply "put together". This may mean a kind of multiplication like in 2 Kg , or in matrix multiplication like A B.

Composed assignment operators

Consider the following operator statement:

mutable let a = 3;

a+=5;

The += is a composed assignment operator. Where the a+=5 statement is equivalent to:

mutable let a : Integer = 3;

a = a + 5;

All composed assignment operator are decomposed by the compiler in an assignment an a call to the root operator.

Table 3. Composed assignment operators

+=

-=

*=

/=

\=

&=

|=

^=

<⇐

>>=

Remember that assignments are statements in Lense, so the following code does not compile:

mutable let a : Integer = 3;

if (a+=5 > 7){ // does not compile
  // do something
}

This one does:

mutable let a : Integer = 3;

a+=5;

if (a > 7){
  // do something
}

A note on Increment and Decrement operators

Lense does not support increment and decrement operators, but options for the most common uses.

for loops

Is common in other languages to use the increment operator in for loop like this:

for (int i = 0; i < someLength; i++){
  ... // do something with i
}

In Lense you can use ranges like:

for (let i in 0 ..< someLength){
  ... // do something with i
}

Arithmetic Increment

Another common use, is to increment a variable when some condition is true, for example, when counting.

int counter = 0;
if (someCondition){
   count++;
}

This can be written using the += composed assignment operator as:

mutable let counter = 0;
if (someCondition){
   count += 1;
}

or if you need to be further explicit:

mutable let counter = 0;
if (someCondition){
   count = count + 1;
}