Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Isolating state mutation #44

Open
njordhov opened this issue Dec 13, 2020 · 0 comments
Open

Isolating state mutation #44

njordhov opened this issue Dec 13, 2020 · 0 comments
Labels
non-backwards-compatible Features that can not be implemented in a backwards-compatible manner

Comments

@njordhov
Copy link

njordhov commented Dec 13, 2020

Clarity is brilliantly influenced by ideas from functional programming. This is a source for important benefits promised by the language, such as predictability, security, decidability, reliability and consistency. It is timely to review the current design to potentially make Clarity even better aligned with its promises.

Functional style programs are easier to reason about, and can more safely be refactored. They make static analysis and type inference less complicated, and are simpler to process for other programs including to lint, transpile, generate, or optimize.

Clarity builds on several concepts from functional programming. It has immutable datastructures: Lists, buffers, strings, and atoms like numbers cannot be mutated but creates new ones, with the previous version still unchanged. Most of Clarity's functions are pure: They have no side effects and deterministically produce the same output for same arguments, making them referentially transparent. Symbols are permanently bound to values; Once defined they do not change their value.

The exception is mutable state stored between transactions, defined with define-map and define-data-var. Mutating state makes the contract sensitive to execution order, cancelling the benefits of pure functions. @jnelson provides an example in stacks-network/stacks-core#1628 where each call to get-inc-foo returns a mutated global variable, confusing the intent and making the program more surprising:

(define-data-var foo uint 1)

(define-private (get-inc-foo) 
  (begin
     (var-set foo (+ 1 (var-get foo)))
     (var-get foo)))

(< (get-inc-foo) (get-inc-foo) (get-inc-foo))  ;; What does this evaluate to?

Mutating state is of course essential to a smart contract as a state machine. Other functions mutating state includes stx-transfer and contract-call to a public function, as well as minting and transfer of tokens.

How can state mutation be improved to support a more functional style? Mutating calls can benefit from being isolated, leaving the heavy lifting to pure functions. Clarity already have basic support for this approach, where the mutations typically are kept in the initially called public function. Proposal stacks-network/stacks-core#1393 is intended to allow serial mutations without relying on map iteration, lifting the mutating call by eliminating the need for a mutating mapper function. Similarly, the map-update suggested in stacks-network/stacks-core#1728 (comment) calls a read-only function that transform the state in the table.

@kantai kantai added the non-backwards-compatible Features that can not be implemented in a backwards-compatible manner label Mar 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
non-backwards-compatible Features that can not be implemented in a backwards-compatible manner
Projects
None yet
Development

No branches or pull requests

2 participants