Operators
Keel provides a rich set of operators for arithmetic, comparison, logical operations, and more.
Arithmetic Operators
Basic mathematical operations:
| Operator | Description | Example |
|---|---|---|
+ | Addition | 1 + 2 |
- | Subtraction | 5 - 3 |
* | Multiplication | 4 * 2 |
/ | Division | 10 / 3 |
// | Integer division | 10 // 3 |
% | Modulo | 10 % 3 |
^ | Power | 2 ^ 8 |
let addition = 5 + 3
let subtraction = 10 - 4
let multiply = 6 * 7
let divide = 15 / 3
let intDiv = 10 // 3
let modulo = 17 % 5
let power = 2 ^ 10
let negative = -42 -- -42 (negation)
power
Try itInteger vs Float Division
Division behavior:
let floatDiv = 10 / 3
let intDiv = 10 // 3
(floatDiv, intDiv)
Try itNumeric Type Promotion
When mixing Int and Float, Int is promoted to Float:
let x = 1 + 2.5
let y = 3 * 1.5
x + y
Try itComparison Operators
Compare values and return Bool:
| Operator | Description | Example |
|---|---|---|
== | Equal | a == b |
!= | Not equal | a != b |
< | Less than | a < b |
<= | Less than or equal | a <= b |
> | Greater than | a > b |
>= | Greater than or equal | a >= b |
let eq = 5 == 5
let neq = 5 != 6
let lt = 3 < 5
let gt = 5 > 3
let lte = 3 <= 3
let gte = 5 >= 4
(eq, neq, lt, gt, lte, gte)
Try it== and != work on all scalar types (Int, Float, Decimal, Bool, String, Char, Symbol, Unit), Enum variants, and structural collections (Tuple, List, Record). They do not work on functions, DataFrames, Matrices, or open record types.
Logical Operators
Boolean logic operations:
| Operator | Description | Example |
|---|---|---|
&& | Logical AND | a && b |
|| | Logical OR | a || b |
not | Logical NOT | not a |
-- And (both must be True)
let and1 = True && True
let and2 = True && False
-- Or (at least one must be True)
let or1 = True || False
let or2 = False || False
-- Not (negation)
let not1 = not True -- False
let not2 = not False -- True
let not3 = not (1 > 2)
-- True
let not4 = not not True -- True (double negation)
(and1, or1, not1, not3)
Try itShort-circuit Evaluation
Logical operators use short-circuit evaluation:
-- False && ... returns False immediately without evaluating the right side
let result = False && True
result -- False
Try itBitwise Operators
Bitwise operations on Int values (two's-complement 64-bit integers):
| Operator | Description | Example |
|---|---|---|
~ | Bitwise NOT | ~n |
~n flips every bit of n. The result satisfies ~~n == n (double inversion is identity) and ~n == -(n + 1) for all Int values.
let n = 42
let inverted = ~n -- -43 (two's-complement bitwise NOT)
let restored = ~inverted -- 42 (double inversion is identity)
restored
Try it~ accepts only Int. Applying it to Float, Decimal, or Bool is a compile error.
String Operators
String manipulation:
"Hello, " ++ "World!"
Try itList Operators
Working with lists:
| Operator | Description | Example |
|---|---|---|
:: | Cons (prepend) | 1 :: [2, 3] |
++ | Concatenation | [1, 2] ++ [3, 4] |
-- Cons (prepend element)
let consed1 = 1 :: [2, 3]
let consed2 : [Int] = 1 :: (2 :: [])
-- Concatenation
let joined = [1, 2] ++ [3, 4]
joined
Try itPipe Operators
Chain function calls elegantly:
| Operator | Description | Example |
|---|---|---|
|> | Pipe forward | x |> f |
<| | Pipe backward | f <| x |
fn double : Int -> Int
fn double x =
x * 2
fn addOne : Int -> Int
fn addOne x =
x + 1
-- Forward pipe
5 |> double -- Same as: double 5 = 10
Try itimport List
-- Pipeline example
[1, 2, 3, 4, 5]
|> List.filter (|x| x > 2)
|> List.map (|x| x * 2)
-- [6, 8, 10]
Try itFunction Composition
Compose functions into new functions:
| Operator | Description | Example |
|---|---|---|
>> | Compose (left to right) | f >> g |
<< | Compose (right to left) | g << f |
-- Forward composition (f first, then g)
let addOne = |x : Int| x + 1
let double = |x : Int| x * 2
let addThenDouble = addOne >> double
addThenDouble 5 -- 12 (5 + 1 = 6, 6 * 2 = 12)
Try it-- Backward composition (g first, then f)
let addOne = |x : Int| x + 1
let double = |x : Int| x * 2
let doubleThenAdd = addOne << double
doubleThenAdd 5 -- 11 (5 * 2 = 10, 10 + 1 = 11)
Try itResult Propagation Operator
The ? postfix operator works on Result values:
| Operator | Description |
|---|---|
? | Unwrap Ok value; short-circuit with Err if the result is an error |
-- ? unwraps Ok values and short-circuits on Err
let addOne = |x: Int| Ok (x + 1)
let result = 5 |> addOne
result?
Try itIf the expression is Ok v, ? evaluates to v. If it is Err e, the expression evaluates to Err e (the error is preserved). Applying ? to a non-Result type is a compile error.
Operators with DataFrame Expressions
Arithmetic (+, -, *, /, %, ^), comparison (==, !=, <, <=, >, >=), boolean (&&, ||), and unary (-, not) operators work directly on Expr values from the DataFrame.Expr module. When either operand is an Expr, scalars are automatically coerced to literal expressions:
-- Operators work directly on column references
import DataFrame
import Result
import DataFrame.Expr as Expr
let df =
DataFrame.fromRecords
[ { price = 10.0, quantity = 2, active = True }
, { price = 20.0, quantity = 3, active = False }
]
-- Arithmetic: @col * scalar -> Expr
-- Comparison: @col >= scalar -> Expr (boolean)
-- Boolean: Expr && Expr -> Expr
let expr = @price * 1.1
df
|> DataFrame.applyExprs [(@adjusted, expr)]
|> Result.andThen (DataFrame.column @adjusted)
|> Result.withDefault []
Try itSee the DataFrame Expressions guide for full details.
Best Practices
- Use parentheses when precedence is unclear
- Prefer pipes (
|>) for data transformation chains - Use composition (
>>) to build reusable function pipelines - Use
!=for inequality (not/=)
Next Steps
Learn about functions to create reusable code.