This site is version 0.0.1. Most of the code on this site is drafted by an agent and needs review. I apologize for the AI-speak, we are working on rewriting it!
viv-lang.org

Python and Viv · Records, classes and tuples

A class with state

What to notice

Open the Viv in the workbench The atlas of the Viv py2viv refuses this Python (the reason is in its column).
Python state.py · 32 lines
Viv state.viv · 44 lines
py2viv refuses state.py
1 a type that holds state
4class Account:
5 def __init__(self, owner, balance):
6 self.owner = owner
7 self.balance = balance
8 self.history = []
2record Account {
3 owner: Str
4 balance: Num
5 history: Arr
6}
7
8def open_account(owner, balance) -> Account {
9 return Account { owner: owner, balance: balance, history: [] }
10}
py2viv refuses this program:
state.py:13:9: not numeric Python: list.append (lifted by R17)
            self.history.append(amount)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  py2viv changes a list (append, pop, insert, extend, remove, clear, reverse, sort) only when nothing else can hold it: bound only to new lists (`[]`, a list literal, a comprehension, list(...), sorted(...)) and read nowhere an alias could be taken (a second name, an argument, a container, a nested def); otherwise allocate the final size with np.zeros(n) and write by index.
state.py:20:9: not numeric Python: list.append (lifted by R17)
            self.history.append(-amount)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  py2viv changes a list (append, pop, insert, extend, remove, clear, reverse, sort) only when nothing else can hold it: bound only to new lists (`[]`, a list literal, a comprehension, list(...), sorted(...)) and read nowhere an alias could be taken (a second name, an argument, a container, a nested def); otherwise allocate the final size with np.zeros(n) and write by index.
2 constructs outside the numeric subset (tools/py2viv/SUBSET.md)
2 operations that change it
11 def deposit(self, amount):
12 self.balance += amount
13 self.history.append(amount)
14
15 def withdraw(self, amount):
16 if amount > self.balance:
17 print(f"refused: {self.owner} has {self.balance:.2f}, asked for {amount:.2f}")
18 return
19 self.balance -= amount
20 self.history.append(-amount)
13def deposit(a: Account, amount) -> Account {
14 history = a.history
15 history = append(history, amount)
16 return Account { balance: a.balance + amount, history: history, ..a }
17}
18
19def withdraw(a: Account, amount) -> Account {
20 if amount > a.balance {
21 print(f"refused: {a.owner} has {a.balance:.2f}, asked for {amount:.2f}")
22 return a
23 }
24 history = a.history
25 history = append(history, -amount)
26 return Account { balance: a.balance - amount, history: history, ..a }
27}
(refused)
3 use it
23acct = Account("Ada", 100.0)
24acct.deposit(25.5)
25acct.withdraw(40.0)
26acct.withdraw(500.0)
27print(f"{acct.owner}: {acct.balance:.2f}")
28print("history:", acct.history)
30acct = open_account("Ada", 100.0)
31acct = acct.deposit(25.5)
32acct = acct.withdraw(40.0)
33acct = acct.withdraw(500.0)
34print(f"{acct.owner}: {acct.balance:.2f}")
35print("history:", fmt(acct.history))
(refused)
4 checks
31assert acct.balance == 85.5
32assert len(acct.history) == 2
38test "account" {
39 assert_true(acct.balance == 85.5)
40 assert_true(len(acct.history) == 2)
41}
(refused)

What they print

identical — the two programs print the same text.

Dimmed lines are left out of the comparison. Each Viv test block prints one PASS line; the Python checks the same facts with assert, which prints nothing.

$ python3 state.py

refused: Ada has 85.50, asked for 500.00
Ada: 85.50
history: [25.5, -40.0]

$ viv run state.viv

refused: Ada has 85.50, asked for 500.00
Ada: 85.50
history: [25.5, -40.0]
PASS account