#set document(title: "2.5 Operators", author: "Modular Inc. / XYZ Homework") #set page(width: 8.5in, height: auto, margin: 1in) #import "@preview/cetz:0.5.2" #set text(font: ("STIX Two Text", "Libertinus Serif", "New Computer Modern"), size: 10.5pt, lang: "en") #show math.equation: set text(font: ("STIX Two Math", "New Computer Modern Math")) #set par(justify: true, leading: 0.62em, spacing: 0.9em) #set enum(spacing: 1.1em) // room between list items so tall inline fractions don't collide #set list(spacing: 1.1em) #set table(stroke: 0.5pt + rgb("#c7ccd3")) #let BLUE = rgb("#183B6F") // brand navy — section bars + example/solution labels (white on navy 11.09:1) #let ORANGE = rgb("#A94509") // brand primary-700 — AA-safe deep orange for TEXT (5.93:1 on white; raw brand #F37021 is 2.94:1 and must never carry text) #let RED = rgb("#DC2626") // brand error-600 #let GREEN = rgb("#059669") // brand success-600 (decoration only; small green text uses green-text #007942) #show heading.where(level: 1): it => block(width: 100%, above: 0pt, below: 16pt, fill: gradient.linear(BLUE, rgb("#2C5AA0")), inset: (x: 14pt, y: 12pt), radius: 3pt, text(fill: white, weight: "bold", size: 19pt, it.body)) #show heading.where(level: 2): it => block(width: 100%, above: 18pt, below: 10pt, fill: BLUE, inset: (x: 10pt, y: 6pt), radius: 2pt, text(fill: white, weight: "bold", size: 12pt, it.body)) #show heading.where(level: 3): it => text(fill: ORANGE, weight: "bold", size: 12.5pt, it.body) #show heading.where(level: 4): it => text(fill: BLUE, weight: "bold", size: 10.5pt, it.body) #let examplebox(label, title, body) = block(width: 100%, breakable: true, fill: rgb("#EFF1F5"), stroke: 0.5pt + rgb("#CFDDF0"), radius: 4pt, inset: 10pt, above: 12pt, below: 12pt)[ #block(below: 6pt)[#box(fill: BLUE, inset: (x: 6pt, y: 2pt), radius: 2pt, text(fill: white, weight: "bold", size: 8.5pt, label)) #h(0.4em) #strong[#title]] #body] // rail = decorative left rule (raw brand token); labelcolor = AA-safe label text shade #let notebox(label, rail, labelcolor, tint, body) = block(width: 100%, breakable: true, fill: tint, stroke: (left: 3pt + rail), inset: (left: 10pt, rest: 8pt), radius: (right: 4pt), above: 11pt, below: 11pt)[ #text(fill: labelcolor, weight: "bold", size: 7.5pt, tracking: 0.5pt)[#upper(label)] #linebreak() #body] #let solutionbox(body) = block(above: 4pt, below: 8pt)[ #text(fill: BLUE, weight: "bold", size: 8.5pt)[Solution] #linebreak() #body] #let figph(msg) = block(width: 100%, height: 60pt, fill: rgb("#f6f7f9"), stroke: (paint: rgb("#c7ccd3"), dash: "dashed"), radius: 4pt, inset: 10pt)[ #align(center + horizon, text(fill: rgb("#889"), style: "italic", size: 9pt, msg))] // Standardize inlined figure sizes: measure the natural CeTZ canvas, then scale to a // consistent envelope (aspect-aware; see build_typst.py FIG_* constants). Unlike the // print preamble, dimensions are FLOORED: in an editor a user can trim a figure to a // degenerate 1-D shape (a bare line), and w/h or tw/w would then divide by zero. #let _STD_W = 3.5 #let _WIDE_W = 5.6 #let _MAX_H = 3.4 #let _ASPECT_WIDE = 2.2 #let _UPSCALE_MAX = 1.15 #let stdfig(body) = context { let m = measure(body) let w = calc.max(m.width / 1in, 0.01) let h = calc.max(m.height / 1in, 0.01) let tw = if w / h > _ASPECT_WIDE { _WIDE_W } else { _STD_W } let s = calc.min(tw / w, _MAX_H / h, _UPSCALE_MAX) align(center, box(scale(x: s * 100%, y: s * 100%, reflow: true, body))) } #show figure: set block(breakable: false) #set figure(gap: 8pt) #show figure.caption: set text(size: 8.5pt, fill: rgb("#555")) == 2.5#h(0.6em)Operators Operators are symbols and keywords that act on values. They support addition, comparison, bitwise operations, and boolean logic using operator syntax instead of method calls. Mojo's operator syntax mirrors Python. Symbols, precedence, and associativity use Python conventions, so most behavior will feel familiar if you've used Python, C, Rust, or similar languages. That said, a few details are specific to Mojo. Boolean operators use words (and, or, not) instead of symbols like && and ||. The ternary expression places the condition in the middle. The caret (^) serves as both bitwise XOR and the transfer sigil for ownership and memory management. === Arithmetic Standard arithmetic operators match what you see in most languages: print(7 + 3) \# 10, add print(7 - 3) \# 4, subtract print(7 \* 3) \# 21, multiply Exponentiation uses two stars (\*\*) and not a caret (^). If you prefer a function form, call pow(base, exponent): print(2 \*\* 8) \# 256 (exponentiation) ==== Unary symbols Three prefix operators apply to single values. Place the operator to the immediate left of the value without spaces: - -x negates (-7, the operator is -, the expression is 7) - +x is a no-op identity (+7) - ~x inverts bits (var a: Int8 = -128; print(~a) \# 127) ==== Division and remainder Mojo has two division operators, and the difference matters for negative numbers: var a = -7 var b = 4 print(a / b) \# -1 (truncates toward zero) print(a // b) \# -2 (rounds toward negative infinity) - Use / when you want truncation toward zero. - Use // when you want floor division. For floating-point types, / performs standard division and // returns a float rounded down to the nearest whole number. The modulo operator % returns the remainder, following this rule: a == b \* (a // b) + (a % b) For example: print(7 % 3) \# 1 print(-7 % 4) \# 1 print(7 % -4) \# -1 ==== Exponentiation The \*\* operator is right-associative, so it groups from the right: print(2 \*\* 3 \*\* 2) \# 512, same as 2 \*\* (3 \*\* 2) Exponentiation is one of only two right-associative operators in Mojo. The other is the ternary conditional expression (if-else). ==== Matrix multiplication The \@ operator performs matrix multiplication. If you've used NumPy, this will look familiar. Mojo doesn't include a built-in matrix type, but any type that implements \_\_matmul\_\_() can use it. === Comparisons Mojo provides six comparison operators: ==, !=, \<, \<=, \>, and \>=. Each returns a Bool value: print(10 \> 5) \# True print(10 == 10) \# True print(10 != 10) \# False ==== Floating-point comparison Don't compare floating-point values with the equality operator (==). Small rounding errors accumulate, and values that look equal often aren't: from std.math import isclose var total: Float64 = 0.0 for \_ in range(10): total += 0.1 print(total == 1.0) \# False Use isclose() for approximate comparison on any floating-point type. print(isclose(total, 1.0)) \# True ==== Chained comparisons You can chain comparisons to check a range or a sequence of conditions in one expression. Each pair is evaluated from left to right: var x = 5 print(1 \< x \< 10) \# True, 1 \< x and x \< 10 print(1 \< x \< 3) \# False, 1 \< x and x \< 3 print(1 \< x \<= 5 \< 9) \# True, 1 \< x and x \<= 5 and 5 \< 9 The expression a \< b \< c is equivalent to (a \< b) and (b \< c). The middle value is evaluated once, not twice. This matters when the value comes from a function: var short\_item\_list = \[1, 2, 3, 4, 5\] var ok = 0 \< len(short\_item\_list) \<= 10 print(ok) \# True Comparison, membership, and identity operators share the same precedence, so you can combine them in a single chain. As a hypothetical example: 5 != a \< b in c is d This evaluates as: (5 != a) and (a \< b) and (b in c) and (c is d) === Bitwise operations Bitwise operators work on integer types at the bit level. They let you inspect and manipulate individual bits directly. AND (&) keeps bits that are set in both operands, OR (|) keeps bits set in either, and XOR (^) keeps bits that differ: var flags: UInt8 = 0b0000\_0101 var mask: UInt8 = 0b0000\_0011 print(flags & mask) \# 1 (only bit 0 set in both) print(flags | mask) \# 7 (bits 0, 1, and 2) print(flags ^ mask) \# 6 (bits 1 and 2 differ) Left shift (\<\<) and right shift (\>\>) move bits by a given number of positions. Left shift by #emph[n] is equivalent to multiplying by 2^n, right shift to dividing by 2^n: print(1 \<\< 4) \# 16 print(16 \>\> 2) \# 4 print(-16 \>\> 2) \# -4 Among the bitwise operators, precedence runs: NOT (~) is tightest, then shift operators, then AND, then XOR, then OR. If the grouping isn't obvious, use parentheses. #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ Mojo uses the caret (^) for both bitwise XOR and the transfer operator. In expressions where the meaning could be ambiguous, Mojo treats it as XOR. For example, x^+1 is (x ^ (+1)), not ((x^) + 1). ] === Boolean logic Mojo uses words for boolean operators instead of symbols like && or ||. The operators read like plain language: print(True and False) \# False print(True or False) \# True print(not True) \# False ==== Short-circuit evaluation The and and or operators stop as soon as the result is known. With and, if the left side is falsy, the right side isn't evaluated. With or, if the left side is truthy, the right side is skipped. def always\_true() -\> Bool: print("called") return True \# The string "called" never prints because the left side \# of \`and\` is already False: print(False and always\_true()) \# False This behavior is useful when the right side has side effects or is expensive to compute. ==== Truthiness Types that conform to Boolable have a truth value, so they can be used directly in boolean expressions and if conditions. The rules are predictable: zero, empty strings, empty collections, and None are falsy. Everything else is truthy. var name = "Mojo" if name: print("Name is set") === Membership and identity ==== in and not in The in operator checks whether a collection contains a value: var colors = \["red", "green", "blue"\] print("red" in colors) \# True print("yellow" not in colors) \# True It also works with strings to check for substrings: var food = "peanut butter" if "nut" in food: print("Contains a nut") \# prints ==== is and is not Identity operators check whether two values refer to the same object, not just whether they are equal. The most common use is checking Optional values against None: var opt: Optional\[Int\] = None if opt is None: print("No value") \# prints opt = 42 if opt is not None: print("Has a value") \# prints === String operators Strings support concatenation with + and repetition with \*: var greeting = "Hello" + " " + "Mojo" print(greeting) \# Hello Mojo print("ha" \* 3) \# hahaha print("=" \* 40) \# a line of 40 equals signs #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ When building a string from multiple values, the multi-argument String() constructor is more efficient than chaining +: var result = String("Point (", x, ", ", y, ")") \# or var result = String(t"Point({x}, {y})") ] Strings compare lexicographically. Uppercase letters sort before lowercase: print("Zebra" \< "ant") \# True print("bird" == "bird") \# True === Conditional expression Mojo uses if-else for conditional expressions instead of ? :. The condition sits in the middle: var score = 80 var result = "pass" if score \> 65 else "fail" print(result) \# pass You can use this form anywhere an expression is valid, including function arguments: def greet(name: String): print("Hello,", name) greet("Sami" if True else "Cass") \# Hello, Sami Like exponentiation, chained ternary expressions are right-associative. They group from the right, which can be hard to read: var value = 50 var label = ( "low" if value \< 10 else "high" if value \> 100 else "mid" ) print(label) \# mid === Assignment operators ==== In-place assignment Most binary operators have a compound assignment form: +=, -=, \*=, /=, //=, %=, \*\*=, \@=, &=, |=, ^=, \<\<=, and \>\>=. These update the left-hand value instead of creating a new one: var count = 0 count += 1 count += 1 print(count) \# 2 var flags: UInt8 = 0b0000\_0001 flags |= 0b0000\_0100 print(flags) \# 5 (bits 0 and 2 set) For types that store data on the heap, in-place operators can avoid allocating intermediate values. A type must implement its in-place methods explicitly, so not every type that supports + also supports +=. ==== Walrus operator The walrus operator (:=, officially an #emph[assignment expression]) assigns a value inside an expression. The assigned value becomes the result of that expression: while (name := input("Name or 'quit': ")) != "quit": print("Hello,", name) === Precedence When an expression mixes operators, precedence determines what runs first. From tightest to loosest, they are: calls and attribute access, exponentiation, unary prefix operators, arithmetic (multiply and divide before add and subtract), shifts, bitwise operators (AND before XOR before OR), comparisons, boolean logic (not before and before or), conditional expression, and the walrus operator. When in doubt, use parentheses. They cost nothing at runtime and make your intent clear both when you write the code and when it is later read and maintained: \# Clear without thinking about precedence var ready = (age \>= 18) and (score \> threshold) Assignment operators (=, +=, -=, and others) are statements, not expressions. You can't mix them into expressions.