chore: retire mdbook
This commit is contained in:
parent
cbe7ad65f7
commit
34d76bc280
|
@ -1,47 +0,0 @@
|
|||
name: mdBook
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v2
|
||||
- name: Setup mdBook
|
||||
uses: peaceiris/actions-mdbook@v1
|
||||
with:
|
||||
mdbook-version: "latest"
|
||||
|
||||
- run: mdbook build book -d ../_site
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
deploy:
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
|
@ -1,5 +1,4 @@
|
|||
version="0.3.2"
|
||||
|
||||
[scripts]
|
||||
book = "mdbook serve book -d ../_site"
|
||||
publish = "cargo workspaces publish"
|
|
@ -1 +0,0 @@
|
|||
book
|
|
@ -1,9 +0,0 @@
|
|||
[book]
|
||||
authors = ["Ch1n3du"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "The Aiken Programming Language"
|
||||
|
||||
[output.html]
|
||||
mathjax-support = true
|
|
@ -1,17 +0,0 @@
|
|||
# Summary
|
||||
|
||||
- [Introduction](./introduction.md)
|
||||
- [Getting Started](./getting-started.md)
|
||||
- [Language Tour](./language-tour.md)
|
||||
- [Primitive Types](./language-tour/primitive-types.md)
|
||||
- [Variables & Constants](./language-tour/variables-and-constants.md)
|
||||
- [Functions](./language-tour/functions.md)
|
||||
- [Custom Types](./language-tour/custom-types.md)
|
||||
- [Control flow](./language-tour/control-flow.md)
|
||||
- [Modules](./language-tour/modules.md)
|
||||
- [Untyped Plutus Core](./uplc.md)
|
||||
- [Syntax](./uplc/syntax.md)
|
||||
- [Command-line utilities](./uplc/cli.md)
|
||||
- [Builtins](./uplc/builtins.md)
|
||||
- [Ecosystem Overview](./ecosystem-overview.md)
|
||||
- [Resources](./resources.md)
|
|
@ -1,161 +0,0 @@
|
|||
# Ecosystem Overview
|
||||
|
||||
Within the Cardano community there has been a flourishing ecosystem
|
||||
of alternative languages for writing smart contracts. So naturally, one might ask about
|
||||
the differences between these and which they should use for their use case. There is
|
||||
also a big misconception about how writing smart contracts actually works on Cardano. In this document,
|
||||
we'll list some of the main alternatives along with their differences and similarities. Before we get into this though, let's discuss the misconception first so everyone is on the same page.
|
||||
|
||||
## The Misconception
|
||||
|
||||
**Cardano uses Haskell for smart contracts**
|
||||
|
||||
This is **not** entirely true.
|
||||
|
||||
The main Cardano node implementation does indeed happen to be written in
|
||||
Haskell. The virtual machine for executing smart contracts that comes baked
|
||||
into the node is then of course also implemented in Haskell. **But** that does
|
||||
not mean that it is Haskell itself which is executed by the smart contract
|
||||
virtual machine. Aiken actually has a fully working version of this [virtual
|
||||
machine](https://github.com/txpipe/aiken/blob/main/crates/uplc/src/machine.rs#L63)
|
||||
written in Rust.
|
||||
|
||||
So what's going on here? What is actually being executed?
|
||||
|
||||
Well, there is something called [Untyped Plutus Core](./uplc.md) which is the
|
||||
lowest level representation of a smart contract and it is this low level
|
||||
representation that actually gets executed by the virtual machine. So contrary
|
||||
to popular knowledge, there isn't actually a coupling to Haskell. Armed with
|
||||
this knowledge one may now ask another question:
|
||||
|
||||
**So what am I writing when I write Plutus?**
|
||||
|
||||
In the wild, Plutus tends to refer to one of three things:
|
||||
|
||||
1. _Plutus Core_, the low-level interpreted code that is executed by the
|
||||
Cardano virtual machine.
|
||||
2. _PlutusTx_, a Haskell framework that compiles to Plutus Core through the
|
||||
means of a GHC plugin.
|
||||
3. The _Plutus Platform_, which more broadly includes _Plutus Core_, _PlutusTx_
|
||||
and most of the tools developed around _Plutus Core_.
|
||||
|
||||
Most of the time, when people say _Plutus_, they mean _PlutusTx_, which has led
|
||||
to a popular belief that Plutus is in fact Haskell.
|
||||
|
||||
_PlutusTx_ being built as a GHC plugin means that you even use Haskell tooling
|
||||
like cabal for it. Even so, you are technically not writing Haskell. Code that
|
||||
one writes using _PlutusTx_ is consumed by the plugin and then transformed into
|
||||
_Untyped Plutus Core_. Essentially, it takes the intermediate representation of
|
||||
Haskell, GHC Core, and turns that into Untyped Plutus Core. This results in not
|
||||
needing to write a new parser and type checker. What you end up with is a kind
|
||||
of embedded language that looks and feels like Haskell but the target runtime
|
||||
is not GHC.
|
||||
|
||||
## The Alternatives
|
||||
|
||||
Now that this misconception is out of the way it should be possible to see how
|
||||
other new languages can be created that ultimately compile to Untyped Plutus
|
||||
Core. The current alternatives range from full blown new languages to embedded
|
||||
Domain Specific Languages (abbrev. eDSLs.) Here is a list of the main ones:
|
||||
|
||||
- [Aiken](https://github.com/txpipe/aiken)
|
||||
- [Helios](https://github.com/Hyperion-BT/Helios)
|
||||
- [Plutarch](https://github.com/Plutonomicon/plutarch-plutus)
|
||||
- [plu-ts](https://github.com/HarmonicLabs/plu-ts)
|
||||
- [Scalus](https://github.com/nau/scalus)
|
||||
|
||||
The creators of each of these projects all know each other and are in open
|
||||
communication with each other.
|
||||
|
||||
### Aiken
|
||||
|
||||
Aiken is a brand new language with it's own syntax and compiler. It is not Rust. The compiler
|
||||
happens to be written in Rust but it is not Rust. Not only is Aiken a compiler
|
||||
for a new language but we've also developed everything in such a way that all
|
||||
the libraries we created in Rust are re-usable by people interested in doing
|
||||
more low-level things. One example of this is
|
||||
[Lucid](https://github.com/spacebudz/lucid), which uses Aiken's
|
||||
[uplc](https://crates.io/crates/uplc) crate to evaluate transactions before
|
||||
submission to calculate exact redeemer ExUnits without using a node, ogmios, or
|
||||
blockfrost.
|
||||
|
||||
As a language, Aiken is purely functional with static typing and type
|
||||
inference. This means most of the time the compiler is smart enough to know
|
||||
what the type of something is without you annotating it. It also let's you make
|
||||
custom types that are similar to records and enums. It does not have
|
||||
higher-kinded types or typeclasses because Aiken aims for simplicity. Writing
|
||||
smart contracts can be tedious, and we therefore believe that a language
|
||||
should remain simple to avoid silly mistakes.
|
||||
|
||||
On-chain scripts are typically small in size and scope (relatively, compared to
|
||||
other kind of applications being written nowadays) and, therefore, do not
|
||||
require as much features as general-purpose languages that have to solve much
|
||||
harder problems.
|
||||
|
||||
That being said Aiken may introduce more elaborate language features (such as
|
||||
type classes/traits) at a later time if it's found that they are extremely
|
||||
useful to developers.
|
||||
|
||||
### Helios
|
||||
|
||||
Helios is also a brand new language. One notable implementation difference is
|
||||
that it's compiler is written in a [single javascript file without
|
||||
dependencies](https://github.com/Hyperion-BT/Helios/blob/main/helios.js).
|
||||
According to the creator, the intention of that was to make the compiler
|
||||
implementation easier to audit.
|
||||
|
||||
As a language, Helios is also purely functional but has limited have type
|
||||
inference. It also supports custom types similar to records and enums.
|
||||
|
||||
Another interesting thing is that because the compiler is a single javascript
|
||||
file it's pretty easy to use Helios from within a javascript project.
|
||||
|
||||
### Plutarch
|
||||
|
||||
Plutarch is **not** a new language. You can consider it an eDSL for creating
|
||||
smart contracts with Haskell. In some ways, Plutarch is what PlutusTx should
|
||||
have been. There is no template Haskell involved.
|
||||
|
||||
Since Plutarch is just Haskell, you have everything available to you. Type
|
||||
inference, typeclasses, higher-kinded types, etc.
|
||||
|
||||
### plu-ts
|
||||
|
||||
plu-ts is **not** a new language. You can consider it an eDSL for creating smart contracts with Typescript.
|
||||
Because of this it's a bit closer to Plutarch conceptually than Aiken or Helios.
|
||||
|
||||
It implements it's own type system and at compile time (js runtime) checks the types to be correct.
|
||||
|
||||
### Scalus
|
||||
|
||||
A Scala implementation of Plutus.
|
||||
|
||||
Scalus is a set of libraries to work with Cardano Untyped Plutus Core that works on both JVM and JavaScript. This includes:
|
||||
|
||||
- Untyped Plutus Core (UPLC) data types and functions
|
||||
- Flat, CBOR, JSON serialization
|
||||
- CEK UPLC evaluation machine including execution cost calculation
|
||||
- UPLC parser and pretty printer
|
||||
- Type safe UPLC expression builder, think of Plutarch
|
||||
- Macros to generate UPLC code from Scala code, think of PlutusTx but simpler
|
||||
|
||||
## Which should you use?
|
||||
|
||||
Only you can decide for yourself which of these fits your needs the best. Each
|
||||
has made some different decisions around design and implementation. Aiken and
|
||||
Helios are on the **new language** end of the spectrum while Plutarch and
|
||||
plu-ts are on the eDSL end. Plutarch has the most expressive type system while
|
||||
Aiken's types are in between Plutarch and Helios.
|
||||
|
||||
Embedded DSLs are nice because they integrate seamlessly with off-chain code and
|
||||
usually allow to reuse existing tools that already work on the host language.
|
||||
|
||||
New languages are nice because they include bespoke checks and functionality
|
||||
specifically for Cardano smart contracts directly in their compilers. While
|
||||
they demand a lot of the tooling to be created anew, they also give the
|
||||
opportunity to address shortcomings of existing tooling in various languages.
|
||||
|
||||
Which best serves your use case is for you to say. Being the maintainers behind
|
||||
Aiken, we can't be fully partial in providing an unbiaised answer. We encourage
|
||||
you to review the documentation, design decisions and overall project to make
|
||||
an informed decision.
|
|
@ -1,28 +0,0 @@
|
|||
# Getting Started
|
||||
|
||||
## Installation
|
||||
|
||||
### From Source
|
||||
|
||||
```console
|
||||
cargo install aiken
|
||||
```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> _`cargo` is a package manager for Rust. To install it, please refer to [the Rust book](https://doc.rust-lang.org/stable/book/ch01-01-installation.html)._
|
||||
|
||||
## QuickStart
|
||||
|
||||
```
|
||||
aiken --help
|
||||
```
|
||||
|
||||
## Editor Integrations
|
||||
|
||||
The following plugins provide syntax highlighting and indentation rules for Aiken.
|
||||
|
||||
| Editor | Plugin |
|
||||
| ---------- | --------------------------------------------------------------------------- |
|
||||
| VSCode | [aiken-lang/editor-integration-vscode](https://github.com/aiken-lang/editor-integration-vscode) |
|
||||
| Vim/Neovim | [aiken-lang/editor-integration-nvim](https://github.com/aiken-lang/editor-integration-nvim) |
|
|
@ -1,46 +0,0 @@
|
|||
# Introduction
|
||||
|
||||
Aiken is a new programming language and toolchain for developing
|
||||
smart contracts on the [Cardano](https://cardano.org) blockchain.
|
||||
|
||||
## Philosophy
|
||||
|
||||
Our main goal is to improve the smart contract development experience for the Cardano blockchain. Aiken takes inspiration from many modern languages such as aiken, rust, and elm which are known for friendly error messages and an overall excellent developer experience. We believe Cardano deserves a dedicated language with these kinds of features, developed in the open with the community.
|
||||
|
||||
## Goals
|
||||
|
||||
- We want an easy and safe way to write Cardano smart contracts. We should be able to get started in minutes not days.
|
||||
- We want a complete and delightful experience. A modern blockchain deserves a modern smart contract toolkit. This includes editor integrations such as LSP and tree-sitter along with fancy error messages.
|
||||
- We want there to be as little configuration as possible. It should work out of the box and have opinionated reasonable conventions established with the community.
|
||||
- We want to have a modular design so that components can be picked and chosen as needed. Like the unix philosophy.
|
||||
|
||||
## Roadmap
|
||||
|
||||
In general, the goal is to port everything we need for plutus to
|
||||
Rust. This will be needed if we ever want to build a full node in
|
||||
Rust. Since we will have these tools natively in Rust, we plan on
|
||||
building a new high level language for writing smart contracts on Cardano.
|
||||
|
||||
- [x] bare minimum toolkit for working with untyped plutus core
|
||||
- [x] serialize plutus core into it's on chain encoding
|
||||
- [x] deserialize the on chain encoding into plutus core
|
||||
- [x] Plutus Core interpreter
|
||||
- [ ] [v0.1.0 - Initial Alpha](https://github.com/txpipe/aiken/milestone/1)
|
||||
- [x] define aiken as a language
|
||||
- [x] implement lexing/parsing with pretty error messages
|
||||
- [x] type checking and inference
|
||||
- [ ] uplc code gen
|
||||
- [ ] [v0.2.0 - Implement standard library
|
||||
](https://github.com/txpipe/aiken/milestone/2)
|
||||
- [ ] [v0.3.0 - Testing Framework](https://github.com/txpipe/aiken/milestone/4)
|
||||
- [ ] [v0.4.0 - Improved Tooling](https://github.com/txpipe/aiken/milestone/3)
|
||||
- [ ] LSP 🚀
|
||||
- [ ] syntax highlighting plugins for editors 🎨
|
||||
|
||||
## Components
|
||||
|
||||
The Aiken project is made up of a few different components. The two most important
|
||||
ones are the high level Aiken language for writing smart contracts and a rust library
|
||||
for working with Untyped Plutus Core.
|
||||
|
||||
Learn more about them in the next sections of this book.
|
|
@ -1,7 +0,0 @@
|
|||
# Language Tour
|
||||
|
||||
In this section we explore the fundamentals of the
|
||||
Aiken language. We'll go over syntax, types, control flow,
|
||||
and more. If you have prior programming experience this should
|
||||
be enough to get you started with Aiken. We also at times touch on
|
||||
how things behave and are represented at runtime.
|
|
@ -1,213 +0,0 @@
|
|||
# Control flow
|
||||
|
||||
## Blocks
|
||||
|
||||
Every block in Aiken is an expression. All expressions in the block are
|
||||
executed, and the result of the last expression is returned.
|
||||
|
||||
```aiken
|
||||
let value: Bool = {
|
||||
"Hello"
|
||||
42 + 12
|
||||
False
|
||||
} // False
|
||||
```
|
||||
|
||||
Expression blocks can be used instead of parenthesis to change the precedence of operations.
|
||||
|
||||
```aiken
|
||||
let celsius = { fahrenheit - 32 } * 5 / 9
|
||||
```
|
||||
|
||||
## Matching
|
||||
|
||||
The `when *expr* is` expression is the most common kind of flow control in Aiken code. It
|
||||
allows us to say "if the data has this shape then do that", which we call
|
||||
_pattern matching_.
|
||||
|
||||
Here we match on an `Int` and return a specific string for the values 0, 1,
|
||||
and 2. The final pattern `n` matches any other value that did not match any of
|
||||
the previous patterns.
|
||||
|
||||
```aiken
|
||||
when some_number is {
|
||||
0 -> "Zero"
|
||||
1 -> "One"
|
||||
2 -> "Two"
|
||||
n -> "Some other number" // This matches anything
|
||||
}
|
||||
```
|
||||
|
||||
Aiken's `when *expr* is` is an expression, meaning it returns a value and can be used
|
||||
anywhere we would use a value. For example, we can name the value of a when
|
||||
expression with a `let` binding.
|
||||
|
||||
```aiken
|
||||
type Answer {
|
||||
Yes
|
||||
No
|
||||
}
|
||||
|
||||
let answer = Yes
|
||||
|
||||
let description =
|
||||
when answer is {
|
||||
Yes -> "It's yes!"
|
||||
No -> "It's not yes."
|
||||
}
|
||||
|
||||
description // => "It's true!"
|
||||
```
|
||||
|
||||
## If-Else
|
||||
|
||||
Pattern matching on a `Bool` value is discouraged and `if / else`
|
||||
expressions should be use instead.
|
||||
|
||||
```aiken
|
||||
let some_bool = True
|
||||
|
||||
if some_bool {
|
||||
"It's true!"
|
||||
else {
|
||||
"It's not true."
|
||||
}
|
||||
```
|
||||
|
||||
Note that, while it may look like an imperative instruction: if this then do
|
||||
that or else do that, it is in fact one single expression. This means, in
|
||||
particular, that the return types of both branches have to match.
|
||||
|
||||
## Destructuring
|
||||
|
||||
A `when *expr* is` expression can be used to destructure values that
|
||||
contain other values, such as tuples and lists.
|
||||
|
||||
```aiken
|
||||
when xs is {
|
||||
[] -> "This list is empty"
|
||||
[a] -> "This list has 1 element"
|
||||
[a, b] -> "This list has 2 elements"
|
||||
_other -> "This list has more than 2 elements"
|
||||
}
|
||||
```
|
||||
|
||||
It's not just the top level data structure that can be pattern matched,
|
||||
contained values can also be matched. This gives `when` the ability to
|
||||
concisely express flow control that might be verbose without pattern matching.
|
||||
|
||||
```aiken
|
||||
when xs is {
|
||||
[[]] -> "The only element is an empty list"
|
||||
[[], ..] -> "The 1st element is an empty list"
|
||||
[[4], ..] -> "The 1st element is a list of the number 4"
|
||||
other -> "Something else"
|
||||
}
|
||||
```
|
||||
|
||||
Pattern matching also works in `let` bindings, though patterns that do not
|
||||
match all instances of that type may result in a runtime error.
|
||||
|
||||
```aiken
|
||||
let [a] = [1] // a is 1
|
||||
let [b] = [1, 2] // Runtime error! The pattern has 1 element but the value has 2
|
||||
```
|
||||
|
||||
## Assigning names to sub-patterns
|
||||
|
||||
Sometimes when pattern matching we want to assign a name to a value while
|
||||
specifying its shape at the same time. We can do this using the `as` keyword.
|
||||
|
||||
```aiken
|
||||
when xs is {
|
||||
[[_, ..] as inner_list] -> inner_list
|
||||
_other -> []
|
||||
}
|
||||
```
|
||||
|
||||
## Checking equality and ordering in patterns
|
||||
|
||||
The `if` keyword can be used to add a guard expression to a when clause. Both
|
||||
the patterns have to match and the guard has to evaluate to `True` for the
|
||||
clause to match. The guard expression can check for equality or ordering for
|
||||
`Int`.
|
||||
|
||||
```aiken
|
||||
when xs is {
|
||||
[a, b, c] if a == b && b != c -> "ok"
|
||||
_other -> "ko"
|
||||
}
|
||||
```
|
||||
|
||||
## Alternative clause patterns
|
||||
|
||||
Alternative patterns can be given for a when clause using the `|` operator. If
|
||||
any of the patterns match then the clause matches.
|
||||
|
||||
Here the first clause will match if the variable `number` holds 2, 4, 6 or 8.
|
||||
|
||||
```aiken
|
||||
when number is {
|
||||
2 | 4 | 6 | 8 -> "This is an even number"
|
||||
1 | 3 | 5 | 7 -> "This is an odd number"
|
||||
_ -> "I'm not sure"
|
||||
}
|
||||
```
|
||||
|
||||
If the patterns declare variables then the same variables must be declared in
|
||||
all patterns, and the variables must have the same type in all the patterns.
|
||||
|
||||
```aiken
|
||||
when list is {
|
||||
[1, x] | x -> x // Error! Int != List(Int)
|
||||
_ -> 0
|
||||
}
|
||||
```
|
||||
|
||||
## Todo
|
||||
|
||||
Aiken's `todo` keyword is used to indicate that some code is not yet finished.
|
||||
|
||||
It can be useful when designing a module, type checking functions and types but
|
||||
leaving the implementation of the functions until later.
|
||||
|
||||
```aiken
|
||||
fn not_sure_yet() -> Int {
|
||||
// The type annotations says this returns an Int, but we don't need
|
||||
// to implement it yet.
|
||||
todo
|
||||
}
|
||||
|
||||
fn idk() {
|
||||
favourite_number() * 2
|
||||
}
|
||||
```
|
||||
|
||||
When this code is built Aiken will type check and compile the code to ensure it
|
||||
is valid, and the `todo` will be replaced with code that crashes the program if
|
||||
that function is run.
|
||||
|
||||
A message can be given as a form of documentation. The message will be traced
|
||||
when the `todo` code is run.
|
||||
|
||||
```aiken
|
||||
fn not_sure_yet() -> Int {
|
||||
todo("Believe in the you that believes in yourself!")
|
||||
}
|
||||
```
|
||||
|
||||
When the compiler finds a `todo` it will print a warning, which can be useful
|
||||
to avoid accidentally forgetting to remove a `todo`.
|
||||
|
||||
The warning also includes the expected type of the expression that needs to
|
||||
replace the `todo`. This can be a useful way of asking the compiler what type
|
||||
is needed if you are ever unsure.
|
||||
|
||||
```aiken
|
||||
fn foo() {
|
||||
my_complicated_function(
|
||||
// What type does this function take again...?
|
||||
todo
|
||||
)
|
||||
}
|
||||
```
|
|
@ -1,270 +0,0 @@
|
|||
# Custom types
|
||||
|
||||
Aiken's custom types are named collections of keys and/or values. They are
|
||||
similar to objects in object oriented languages, though they don't have
|
||||
methods.
|
||||
|
||||
Custom types are defined with the `type` keyword.
|
||||
|
||||
```aiken
|
||||
type Datum {
|
||||
Datum { signer: ByteArray, count: Int }
|
||||
}
|
||||
```
|
||||
|
||||
Here we have defined a custom type called `Datum`. Its constructor is called
|
||||
`Datum` and it has two fields: A `signer` field which is a `ByteArray`, and a
|
||||
`count` field which is an `Int`.
|
||||
|
||||
Once defined the custom type can be used in functions:
|
||||
|
||||
```aiken
|
||||
fn datums() {
|
||||
// Fields can be given in any order
|
||||
let datum1 = Datum { signer: #[0xAA, 0xBB], count: 2001 }
|
||||
let datum2 = Datum { count: 1805, name: #[0xAA, 0xCC] }
|
||||
|
||||
[cat1, cat2]
|
||||
}
|
||||
```
|
||||
|
||||
## Multiple constructors
|
||||
|
||||
Custom types in Aiken can be defined with multiple constructors, making them a
|
||||
way of modeling data that can be one of a few different variants.
|
||||
|
||||
We've already seen a custom type with multiple constructors in the Language
|
||||
Tour - [`Bool`](./primitive-types.md#bool).
|
||||
|
||||
Aiken's built-in `Bool` type is defined like this:
|
||||
|
||||
```aiken
|
||||
/// A Bool is a value that is either `True` or `False`
|
||||
type Bool {
|
||||
True
|
||||
False
|
||||
}
|
||||
```
|
||||
|
||||
It's a simple custom type with constructors that takes no arguments at all!
|
||||
Use it to answer yes/no questions and to indicate whether something is `True`
|
||||
or `False`.
|
||||
|
||||
The records created by different constructors for a custom type can contain
|
||||
different values. For example a `User` custom type could have a `LoggedIn`
|
||||
constructor that creates records with a name, and a `Guest` constructor which
|
||||
creates records without any contained values.
|
||||
|
||||
```aiken
|
||||
type User {
|
||||
LoggedIn { count: Int } // A logged in user
|
||||
Guest // A guest user with no details
|
||||
}
|
||||
```
|
||||
|
||||
```aiken
|
||||
let user1 = LoggedIn { count: 4 }
|
||||
let user2 = LoggedIn { count: 2 }
|
||||
let visitor = Guest
|
||||
```
|
||||
|
||||
### Option
|
||||
|
||||
We define `Option` as a generic type like so:
|
||||
|
||||
```
|
||||
type Option<a> {
|
||||
None
|
||||
Some(a)
|
||||
}
|
||||
```
|
||||
|
||||
Then, functions which fail may safely return an optional value.
|
||||
|
||||
```
|
||||
fn get_head(a: List<a>) -> Option<a {
|
||||
when a is {
|
||||
[a, .._] -> Some(a)
|
||||
[] -> None
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `Option` type is readily available in Aiken; it is part of the default types and values available by default. Don't hesitate to use it!
|
||||
|
||||
|
||||
## Destructuring
|
||||
|
||||
When given a custom type record we can pattern match on it to determine which
|
||||
record constructor matches, and to assign names to any contained values.
|
||||
|
||||
```aiken
|
||||
fn get_name(user) {
|
||||
when user is {
|
||||
LoggedIn { count } -> count
|
||||
Guest -> "Guest user"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Custom types can also be destructured with a `let` binding.
|
||||
|
||||
```aiken
|
||||
type Score {
|
||||
Points(Int)
|
||||
}
|
||||
|
||||
let score = Points(50)
|
||||
let Points(p) = score // This brings a let-binding `p` in scope.
|
||||
|
||||
p // 50
|
||||
```
|
||||
|
||||
During destructuring you may also use discards (`_`) or spreads (`..`).
|
||||
|
||||
```aiken
|
||||
type Dog {
|
||||
Dog { name: ByteArray, cuteness: Int, age: Int }
|
||||
}
|
||||
|
||||
let dog = Dog { name: #[67, 97, 115, 104, 101, 119], cuteness: 9001, age: 3 }
|
||||
```
|
||||
|
||||
You will need to specify all args for a pattern match, or alternatively use the
|
||||
spread operator.
|
||||
|
||||
```aiken
|
||||
// All fields present
|
||||
let Dog { name: name, cuteness: _, age: _ } = dog
|
||||
builtin.decode_utf8(name) // "Cashew"
|
||||
|
||||
// Other fields ignored by spreading.
|
||||
// Field punning is supported. Hence `age` is a shorthand for `age: age`.
|
||||
let Dog { age, .. } = dog
|
||||
age // 3
|
||||
```
|
||||
|
||||
## Named accessors
|
||||
|
||||
If a custom type has only one variant and named fields they can be accessed
|
||||
using `.field_name`.
|
||||
|
||||
For example using the `Dog` type defined earlier.
|
||||
|
||||
```aiken
|
||||
let dog = Dog { name: #[82, 105, 110], cuteness: 2001 }
|
||||
dog.cuteness // This returns 2001
|
||||
```
|
||||
|
||||
This is actually so common that Aiken has some syntactic sugar for defining these
|
||||
kinds of single-constructor types: one can omit the constructor:
|
||||
|
||||
```aiken
|
||||
type Dog {
|
||||
name: ByteArray,
|
||||
cuteness: Int,
|
||||
age: Int,
|
||||
}
|
||||
```
|
||||
|
||||
## Generics
|
||||
|
||||
Custom types can be be parameterised with other types, making their contents
|
||||
variable.
|
||||
|
||||
For example, this `Box` type is a simple record that holds a single value.
|
||||
|
||||
```aiken
|
||||
type Box<inner_type> {
|
||||
Box(inner: inner_type)
|
||||
}
|
||||
```
|
||||
|
||||
The type of the field `inner` is `inner_type`, which is a parameter of the `Box`
|
||||
type. If it holds an int the box's type is `Box<Int>`, if it holds a string the
|
||||
box's type is `Box<String>`.
|
||||
|
||||
```aiken
|
||||
fn foo() {
|
||||
let a = Box(420) // type is Box<Int>
|
||||
let b = Box("That's my ninja way!") // type is Box<String>
|
||||
}
|
||||
```
|
||||
|
||||
## Record updates
|
||||
|
||||
Aiken provides a dedicated syntax for updating some of the fields of a custom
|
||||
type record.
|
||||
|
||||
```aiken
|
||||
type Person {
|
||||
name: ByteArray,
|
||||
shoe_size: Int,
|
||||
age: Int,
|
||||
is_happy: Bool,
|
||||
}
|
||||
|
||||
fn have_birthday(person) {
|
||||
// It's this person's birthday, so increment their age and
|
||||
// make them happy
|
||||
Person { ..person, age: person.age + 1, is_happy: True }
|
||||
}
|
||||
```
|
||||
|
||||
The update syntax created a new record with the values of the initial record.
|
||||
It replaces the given binding with their new values.
|
||||
|
||||
## Type aliases
|
||||
|
||||
A type alias lets you create a name which is identical to
|
||||
another type, without any additional information.
|
||||
|
||||
```aiken
|
||||
type MyNumber = Integer
|
||||
```
|
||||
|
||||
They are most useful for simplifying type signatures.
|
||||
|
||||
```aiken
|
||||
type Person = (String, Integer)
|
||||
|
||||
fn create_person(name: String, age: Integer) -> Person {
|
||||
#(name, age)
|
||||
}
|
||||
```
|
||||
|
||||
## Data
|
||||
|
||||
At runtime custom types become an opaque Plutus' Data. In Aiken's type system
|
||||
`Data` matches with any user-defined type (but with none of the primitive
|
||||
types).
|
||||
|
||||
Thus, it's also possible to cast any `Data` to a [custom
|
||||
type](./custom-types.md), and vice versa.
|
||||
|
||||
```aiken
|
||||
fn to_datum(datum: Data) -> Datum {
|
||||
let d: Datum = datum
|
||||
d
|
||||
}
|
||||
```
|
||||
|
||||
Note that this conversion will fail if the given `Data` isn't actually a valid
|
||||
representation of the target type. The primary use-case here is for
|
||||
instantiating script contexts, datums and redeemers provided to scripts in an
|
||||
opaque fashion.
|
||||
|
||||
It's also useful for interacting with builtins that operate on raw `Data`. In
|
||||
this case, the conversation happens implicitly. Simply expect any function that
|
||||
accept `Data` to automatically work on any custom type.
|
||||
|
||||
```aiken
|
||||
type Datum {
|
||||
count: Int,
|
||||
}
|
||||
|
||||
let datum = Datum { count: 1 }
|
||||
|
||||
// fn(Data) -> ByteArray
|
||||
builtin.serialize_data(datum) // some bytearray
|
||||
```
|
|
@ -1,230 +0,0 @@
|
|||
# Functions
|
||||
|
||||
## Named functions
|
||||
|
||||
Named functions in Aiken are defined using the `fn` keyword. Functions can have (typed) arguments, and always have a return type. Because in Aiken, pretty much everything is an expression, functions do not have an explicit _return_ keyword. Instead, they implicitly return whatever they evaluate to.
|
||||
|
||||
```aiken
|
||||
fn add(x: Int, y: Int) -> Int {
|
||||
x + y
|
||||
}
|
||||
|
||||
fn multiply(x: Int, y: Int) -> Int {
|
||||
x * y
|
||||
}
|
||||
```
|
||||
|
||||
Functions are first class values and so can be assigned to variables, passed to
|
||||
other functions, or anything else you might do with any other data type.
|
||||
|
||||
```aiken
|
||||
/// This function takes a function as an argument
|
||||
fn twice(f: fn(t) -> t, x: t) -> t {
|
||||
f(f(x))
|
||||
}
|
||||
|
||||
fn add_one(x: Int) -> Int {
|
||||
x + 1
|
||||
}
|
||||
|
||||
fn add_two(x: Int) -> Int {
|
||||
twice(add_one, x)
|
||||
}
|
||||
```
|
||||
|
||||
## Pipe Operator
|
||||
|
||||
Aiken provides syntax for passing the result of one function to the arguments of another function, the pipe operator (`|>`). This is similar in functionality to the same operator in Elixir or F#.
|
||||
|
||||
The pipe operator allows you to chain function calls without using a lot of parenthesis and nesting.
|
||||
For a simple example, consider the following implementation of an imaginary `string.reverse` in Aiken:
|
||||
|
||||
```aiken
|
||||
string_builder.to_string(string_builder.reverse(string_builder.from_string(string)))
|
||||
```
|
||||
|
||||
This can be expressed more naturally using the pipe operator, eliminating the need to track parenthesis closure.
|
||||
|
||||
```aiken
|
||||
string
|
||||
|> string_builder.from_string
|
||||
|> string_builder.reverse
|
||||
|> string_builder.to_string
|
||||
```
|
||||
|
||||
Each line of this expression applies the function to the result of the previous line. This works easily because each of these functions takes only one argument. Syntax is available to substitute specific arguments of functions that take more than one argument; for more, look below in the section "Function capturing".
|
||||
|
||||
## Type annotations
|
||||
|
||||
Function arguments are normally annotated with their type, and the
|
||||
compiler will check these annotations and ensure they are correct.
|
||||
|
||||
```aiken
|
||||
fn identity(x: some_type) -> some_type {
|
||||
x
|
||||
}
|
||||
|
||||
fn inferred_identity(x) {
|
||||
x
|
||||
}
|
||||
```
|
||||
|
||||
The Aiken compiler can infer all the types of Aiken code without annotations
|
||||
and both annotated and unannotated code is equally safe. It's considered a
|
||||
best practice to always write type annotations for your functions as they
|
||||
provide useful documentation, and they encourage thinking about types as code
|
||||
is being written.
|
||||
|
||||
## Generic functions
|
||||
|
||||
At times you may wish to write functions that are generic over multiple types.
|
||||
For example, consider a function that consumes any value and returns a list
|
||||
containing two of the value that was passed in. This can be expressed in Aiken
|
||||
like this:
|
||||
|
||||
```aiken
|
||||
fn list_of_two(my_value: a) -> List<a> {
|
||||
[my_value, my_value]
|
||||
}
|
||||
```
|
||||
|
||||
Here the type variable `a` is used to represent any possible type.
|
||||
|
||||
You can use any number of different type variables in the same function. This
|
||||
function declares type variables `a` and `b`.
|
||||
|
||||
```aiken
|
||||
fn multi_result(x: a, y: b, condition: Bool) -> Result<a, b> {
|
||||
when condition is {
|
||||
True -> Ok(x)
|
||||
False -> Error(y)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Type variables can be named anything, but the names must be lowercase and may
|
||||
contain underscores. Like other type annotations, they are completely optional,
|
||||
but may aid in understanding the code.
|
||||
|
||||
## Labeled arguments
|
||||
|
||||
When functions take several arguments it can be difficult for the user to
|
||||
remember what the arguments are, and what order they are expected in.
|
||||
|
||||
To help with this Aiken supports _labeled arguments_, where function
|
||||
arguments are given an external label in addition to their internal name.
|
||||
|
||||
Take this function that replaces sections of a string:
|
||||
|
||||
```aiken
|
||||
fn replace(string: String, pattern: String, replacement: String) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
It can be given labels like so.
|
||||
|
||||
```aiken
|
||||
fn replace(
|
||||
in string: String,
|
||||
each pattern: String,
|
||||
with replacement: String,
|
||||
) {
|
||||
// The variables `string`, `pattern`, and `replacement` are in scope here
|
||||
}
|
||||
```
|
||||
|
||||
These labels can then be used when calling the function.
|
||||
|
||||
```aiken
|
||||
replace(in: "A,B,C", each: ",", with: " ")
|
||||
|
||||
// Labeled arguments can be given in any order
|
||||
replace(each: ",", with: " ", in: "A,B,C")
|
||||
|
||||
// Arguments can still be given in a positional fashion
|
||||
replace("A,B,C", ",", " ")
|
||||
```
|
||||
|
||||
The use of argument labels can allow a function to be called in an expressive,
|
||||
sentence-like manner, while still providing a function body that is readable
|
||||
and clear in intent.
|
||||
|
||||
## Anonymous functions
|
||||
|
||||
Anonymous functions can be defined with a similar syntax.
|
||||
|
||||
```aiken
|
||||
fn run() {
|
||||
let add = fn(x, y) { x + y }
|
||||
|
||||
add(1, 2)
|
||||
}
|
||||
```
|
||||
|
||||
## Function capturing
|
||||
|
||||
There is a shorthand syntax for creating anonymous functions that take one
|
||||
argument and call another function. The `_` is used to indicate where the
|
||||
argument should be passed.
|
||||
|
||||
```aiken
|
||||
fn add(x, y) {
|
||||
x + y
|
||||
}
|
||||
|
||||
fn run() {
|
||||
let add_one = add(1, _)
|
||||
|
||||
add_one(2)
|
||||
}
|
||||
```
|
||||
|
||||
The function capture syntax is often used with the pipe operator to create
|
||||
a series of transformations on some data.
|
||||
|
||||
```aiken
|
||||
fn add(x: Int , y: Int ) -> Int {
|
||||
x + y
|
||||
}
|
||||
|
||||
fn run() {
|
||||
// This is the same as add(add(add(1, 3), 6), 9)
|
||||
1
|
||||
|> add(_, 3)
|
||||
|> add(_, 6)
|
||||
|> add(_, 9)
|
||||
}
|
||||
```
|
||||
|
||||
In fact, this usage is so common that there is a special shorthand for it.
|
||||
|
||||
```aiken
|
||||
fn run() {
|
||||
// This is the same as the example above
|
||||
1
|
||||
|> add(3)
|
||||
|> add(6)
|
||||
|> add(9)
|
||||
}
|
||||
```
|
||||
|
||||
The pipe operator will first check to see if the left hand value could be used
|
||||
as the first argument to the call, e.g. `a |> b(1, 2)` would become `b(a, 1, 2)`.
|
||||
|
||||
If not it falls back to calling the result of the right hand side as a function
|
||||
, e.g. `b(1, 2)(a)`.
|
||||
|
||||
## Documentation
|
||||
|
||||
You may add user facing documentation in front of function definitions with a
|
||||
documentation comment `///` per line. Markdown is supported and this text
|
||||
will be included with the module's entry in generated HTML documentation.
|
||||
|
||||
```aiken
|
||||
/// Always true.
|
||||
///
|
||||
fn always_true(_a) -> Bool {
|
||||
True
|
||||
}
|
||||
```
|
|
@ -1,194 +0,0 @@
|
|||
# Modules
|
||||
|
||||
Aiken programs are made up of bundles of functions and types called modules.
|
||||
Each module has its own namespace and can export types and values to be used
|
||||
by other modules in the program.
|
||||
|
||||
```aiken
|
||||
// inside module lib/straw_hats/sunny.ak
|
||||
|
||||
fn count_down() {
|
||||
"3... 2... 1..."
|
||||
}
|
||||
|
||||
fn blast_off() {
|
||||
"BOOM!"
|
||||
}
|
||||
|
||||
pub fn set_sail() {
|
||||
[
|
||||
count_down(),
|
||||
blast_off(),
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Here we can see a module named `straw_hats/sunny`, the name determined by the
|
||||
filename `lib/straw_hats/sunny.ak`. Typically all the modules for one
|
||||
project would live within a directory with the name of the project, such as
|
||||
`straw_hats` in this example.
|
||||
|
||||
The `pub` keyword makes this type usable from other modules.
|
||||
|
||||
For the functions `count_down` and `blast_off` we have omitted the `pub`
|
||||
keyword, so these functions are _private_ module functions. They can only be
|
||||
called by other functions within the same module.
|
||||
|
||||
Functions, type-aliases and constants can all be exported from a module using
|
||||
the `pub` keyword.
|
||||
|
||||
## Import
|
||||
|
||||
To use functions or types from another module we need to import them using the
|
||||
`use` keyword.
|
||||
|
||||
```aiken
|
||||
// inside module src/straw_hats/laugh_tale.ak
|
||||
|
||||
use straw_hats/sunny
|
||||
|
||||
pub fn find_the_one_piece() {
|
||||
sunny.set_sail()
|
||||
}
|
||||
```
|
||||
|
||||
The definition `use straw_hats/sunny` creates a new variable with the name
|
||||
`sunny` and the value of the `sunny` module.
|
||||
|
||||
In the `find_the_one_piece` function we call the imported module's public `set_sail`
|
||||
function using the `.` operator. If we had attempted to call `count_down` it
|
||||
would result in a compile time error as this function is private to the
|
||||
`sunny` module.
|
||||
|
||||
## Named import
|
||||
|
||||
It is also possible to give a module a custom name
|
||||
when importing it using the `as` keyword.
|
||||
|
||||
```aiken
|
||||
use unix/dog
|
||||
use animal/dog as kitty
|
||||
```
|
||||
|
||||
This may be useful to differentiate between multiple modules that would have
|
||||
the same default name when imported.
|
||||
|
||||
## Unqualified import
|
||||
|
||||
Values and types can also be imported in an unqualified fashion.
|
||||
|
||||
```aken
|
||||
use animal/dog.{Dog, stroke}
|
||||
|
||||
pub fn foo() {
|
||||
let puppy = Dog { name: "Zeus" }
|
||||
stroke(puppy)
|
||||
}
|
||||
```
|
||||
|
||||
This may be useful for values that are used frequently in a module, but
|
||||
generally qualified imports are preferred as it makes it clearer where the
|
||||
value is defined.
|
||||
|
||||
## Opaque types
|
||||
|
||||
At times it may be useful to create a type and make the constructors and
|
||||
fields private so that users of this type can only use the type through
|
||||
publically exported functions.
|
||||
|
||||
For example we can create a `Counter` type which holds an int which can be
|
||||
incremented. We don't want the user to alter the int value other than by
|
||||
incrementing it, so we can make the type opaque to prevent them from being
|
||||
able to do this.
|
||||
|
||||
```aiken
|
||||
// The type is defined with the opaque keyword
|
||||
pub opaque type Counter {
|
||||
Counter(value: Int)
|
||||
}
|
||||
|
||||
pub fn new() {
|
||||
Counter(0)
|
||||
}
|
||||
|
||||
pub fn increment(counter: Counter) {
|
||||
Counter(counter.value + 1)
|
||||
}
|
||||
```
|
||||
|
||||
Because the `Counter` type has been marked as `opaque` it is not possible for
|
||||
code in other modules to construct or pattern match on counter values or
|
||||
access the `value` field. Instead other modules have to manipulate the opaque
|
||||
type using the exported functions from the module, in this case `new` and
|
||||
`increment`.
|
||||
|
||||
## The prelude module
|
||||
|
||||
There are two modules that are built into the language, the first is the
|
||||
`aiken` prelude module. By default its types and values are automatically
|
||||
imported into every module you write, but you can still chose to import it the
|
||||
regular way. This may be useful if you have created a type or value with the
|
||||
same name as an item from the prelude.
|
||||
|
||||
```aiken
|
||||
use aiken
|
||||
|
||||
/// This definition locally overrides the `Option` type
|
||||
/// and the `Some` constructor.
|
||||
pub type Option {
|
||||
Some
|
||||
}
|
||||
|
||||
/// The original `Option` and `Some` can still be used
|
||||
pub fn go() -> aiken.Option<Int> {
|
||||
aiken.Some(1)
|
||||
}
|
||||
```
|
||||
|
||||
The prelude module contains these types:
|
||||
|
||||
- `ByteArray`
|
||||
- `Bool`
|
||||
- `Int`
|
||||
- `List<element>`
|
||||
- `Nil`
|
||||
- `Option<value>`
|
||||
- `String`
|
||||
- `Data`
|
||||
|
||||
And these values:
|
||||
|
||||
- `None`
|
||||
- `Some`
|
||||
- `False`
|
||||
- `True`
|
||||
- `Nil`
|
||||
|
||||
## The builtin module
|
||||
|
||||
The second module that comes with the language is for exposing useful builtin
|
||||
functions from Plutus core. Most underlying platform functions are available
|
||||
here using a snake case name. Much of Aiken's syntax ends up compiling to
|
||||
combinations of certain bultins but many aren't "exposed" through the syntax
|
||||
and need to be used directly. The standard library wraps these in a more
|
||||
Aiken-friendly interface so you'll probably never need to use these directly
|
||||
unless you're making your own standard library.
|
||||
|
||||
```aiken
|
||||
use aiken/builtin
|
||||
|
||||
fn eq(a, b) {
|
||||
builtin.equals_integer(a, b)
|
||||
}
|
||||
|
||||
// is implicitly used when doing:
|
||||
|
||||
a == b
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
You may add user facing documentation at the head of modules with a module
|
||||
documentation comment `////` (quadruple slash!) per line. Markdown is supported
|
||||
and this text block will be included with the module's entry in generated HTML
|
||||
documentation.
|
|
@ -1,175 +0,0 @@
|
|||
# Primitive Types
|
||||
|
||||
Aiken has 5 primitive types that are built in the language and can be typed as literals: booleans, integers, strings, byte arrays and data. The language also includes 2 base building blocks for associating types together: lists and tuples.
|
||||
|
||||
Worry not, we'll see later in this manual how to create your own custom types.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> Inline comments are denoted via `//`. We'll use them to illustrate the value
|
||||
> of some expressions in examples given all across this guide.
|
||||
|
||||
## Bool
|
||||
|
||||
A `Bool` is a boolean value that can be either `True` or `False`.
|
||||
|
||||
Aiken defines a handful of operators that work with booleans. No doubts that they'll look
|
||||
quite familiar.
|
||||
|
||||
| Operator | Description |
|
||||
| --- | --- |
|
||||
| `&&` | Logical conjunction (a.k.a 'AND') |
|
||||
| <code>\|\|</code> | Logical disjunction (a.k.a. 'OR') |
|
||||
| `==` | Equality |
|
||||
| `!` | Logical negatation (a.k.a 'NOT') |
|
||||
|
||||
|
||||
## Int
|
||||
|
||||
Aiken's only number type is an arbitrary sized integer. This means there is no underflow or overflow.
|
||||
|
||||
```aiken
|
||||
42
|
||||
14
|
||||
1337
|
||||
```
|
||||
|
||||
Literals can also be written with `_` as separators to enhance readability:
|
||||
|
||||
```
|
||||
1_000_000
|
||||
```
|
||||
|
||||
Aiken also supports writing integer literals in other bases than decimals. Binary, octal, and hexadecimal integers begin with `0b`, `0o`, and `0x` respectively.
|
||||
|
||||
```aiken
|
||||
0b00001111 == 15
|
||||
0o17 == 15
|
||||
0xF == 15
|
||||
```
|
||||
|
||||
Aiken has several binary arithmetic operators that work with integers.
|
||||
|
||||
| Operator | Description |
|
||||
| --- | --- |
|
||||
| `+` | Arithmetic sum |
|
||||
| `-` | Arithmetic difference |
|
||||
| `/` | Whole division |
|
||||
| `*` | Arithmetic multiplication |
|
||||
| `%` | Remainder by whole division |
|
||||
|
||||
Integers are also of course comparable, so they work with a variety of binary logical operators too:
|
||||
|
||||
| Operator | Description |
|
||||
| --- | --- |
|
||||
| `==` | Equality |
|
||||
| `>` | Greater than |
|
||||
| `<` | Smaller than |
|
||||
| `>=` | Greater or equal |
|
||||
| `<=` | Smaller or equal |
|
||||
|
||||
## String
|
||||
|
||||
In Aiken Strings can be written as text surrounded by double quotes.
|
||||
|
||||
```aiken
|
||||
"Hello, Aiken!"
|
||||
```
|
||||
|
||||
They can span multiple lines.
|
||||
|
||||
```aiken
|
||||
"Hello
|
||||
Aiken!"
|
||||
```
|
||||
|
||||
Under the hood text strings are [UTF-8](https://en.wikipedia.org/wiki/UTF-8) encoded binaries
|
||||
and can contain any valid unicode.
|
||||
|
||||
```aiken
|
||||
"🌘 アルバイト Aiken 🌒"
|
||||
```
|
||||
|
||||
## ByteArray
|
||||
|
||||
A _ByteArray_ is exactly what it seems, an array of bytes.
|
||||
|
||||
Aiken supports byte arrays literals, written as lists of integers ranging from 0 to 255 (a.k.a _bytes_):
|
||||
|
||||
```aiken
|
||||
#[10, 255]
|
||||
#[1, 256] // results in a parse error because 256 is bigger than 1 byte
|
||||
```
|
||||
|
||||
It's important to mention that variables and patterns are not supported in
|
||||
byte array literals due to how Untyped Plutus Core works behind the scene.
|
||||
|
||||
```aiken
|
||||
let x = 10
|
||||
#[x, 243] // not allowed
|
||||
```
|
||||
|
||||
However, syntax rules for literal integers also apply to byte arrays. Thus, the following is a perfectly valid syntax:
|
||||
|
||||
```aiken
|
||||
#[0xFF, 0x42]
|
||||
```
|
||||
|
||||
## Data
|
||||
|
||||
A _Data_ is an opaque compound type that can represent any possible user-defined type in Aiken. We'll see later how Aiken lets you compose your own types from the primitives we just presented. In the meantime, think of _Data_ as a kind of wildcard that can possibly represent _any_ value.
|
||||
|
||||
This is useful when you need to use values from different types in an homogeneous structure. Any user-defined type can be cast to a _Data_, and you can try converting from a _Data_ to any custom type in a safe manner.
|
||||
|
||||
Besides, several language builtins only work with _Data_ as a way to deal with polymorphism.
|
||||
|
||||
We'll see more about _Data_ when we cover custom types.
|
||||
|
||||
## Tuples
|
||||
|
||||
Aiken has tuples which can be useful for grouping values. Each element in a tuple can have a different type.
|
||||
|
||||
```aiken
|
||||
(10, "hello") // Type is (Int, String)
|
||||
(1, 4, [0]) // Type is (Int, Int, List(Int))
|
||||
```
|
||||
|
||||
Long tuples (i.e. more than 3 elements) are usually discouraged. Indeed, tuples are anonymous constructors, and while they are quick and easy to use, they often impede readability. When types become more complex, one should use records instead (as we'll see later).
|
||||
|
||||
Elements of a tuple can be accessed using the dot, followed by the index of the element (0-based indexing). So for example:
|
||||
|
||||
```aiken
|
||||
let point = (14, 42)
|
||||
let x = point.0
|
||||
let y = point.1
|
||||
(y, x) // (42, 14)
|
||||
```
|
||||
|
||||
## List
|
||||
|
||||
Lists are ordered collections of values. They're one of the most common data structures in Aiken.
|
||||
|
||||
Unlike tuples, all the elements of a List must be of the same type. Attempting to make a list using multiple
|
||||
different types will result in a type error.
|
||||
|
||||
```aiken
|
||||
[1, 2, 3, 4] // List(Int)
|
||||
|
||||
["text", 3, 4] // Type error!
|
||||
```
|
||||
|
||||
Inserting at the front of a list is very fast, and is the preferred way to add new values.
|
||||
|
||||
```aiken
|
||||
[1, ..[2, 3]] // [1, 2, 3]
|
||||
```
|
||||
|
||||
Note that all data structures in Aiken are immutable so prepending to a list does not change the original list. Instead it efficiently creates a new list with the new additional element.
|
||||
|
||||
```
|
||||
let x = [2, 3]
|
||||
let y = [1, ..x]
|
||||
|
||||
x // [2, 3]
|
||||
y // [1, 2, 3]
|
||||
```
|
|
@ -1,61 +0,0 @@
|
|||
# Variables & Constants
|
||||
|
||||
## Variables
|
||||
|
||||
Aiken has let-bindings for variables. A value can be given a name using the keyword `let`.
|
||||
Names can be reused by later let-bindings.
|
||||
|
||||
Values assigned to let-bindings are immutable, however new bindings can shadow
|
||||
previous bindings.
|
||||
|
||||
```aiken
|
||||
let x = 1
|
||||
let y = x
|
||||
let x = 2
|
||||
|
||||
y + x == 3
|
||||
```
|
||||
|
||||
## Constants
|
||||
|
||||
Let-bindings aren't allowed in a top-level Aiken module. Yet, Aiken provides
|
||||
module constants as a way to use certain fixed values in multiple places of a
|
||||
Aiken project.
|
||||
|
||||
```aiken
|
||||
const start_year = 2101
|
||||
const end_year = 2111
|
||||
```
|
||||
|
||||
Like all values in Aiken, constants are immutable. They cannot be used as global mutable state. When a constant is referenced, its value is inlined by the compiler so they can be used in any place where you'd have written a literal in the first place (e.g. when-expression guards, if clauses ...). We'll see some example of that when dealing with control flows.
|
||||
|
||||
## Type annotations
|
||||
|
||||
Variables and constants can be given type annotations. These annotations serve as documentation or can be used to provide a more specific type than the compiler would otherwise infer.
|
||||
|
||||
```aiken
|
||||
const name: String = "Aiken"
|
||||
const size: Int = 100
|
||||
|
||||
let result: Bool = 14 > 42
|
||||
```
|
||||
|
||||
## assert
|
||||
|
||||
Sometimes, it is useful to fail the exit and fail the execution of a program
|
||||
early on. This is where the `assert` keywords comes in handy. `assert` causes
|
||||
the script to fail if it fails to bind a given expression.
|
||||
|
||||
It is particularly useful when combined with custom types that can have
|
||||
multiple constructors, and that are expected to have a very specific shape. It
|
||||
is primarily used for validating datums and redeemers -- which can really be
|
||||
anything.
|
||||
|
||||
Having ways to enforce that some inputs have the requested shape is thus
|
||||
often necessary. For example, let's consider we have a custom type `MyDatum`
|
||||
that we want to turn some opaque `Data` into. We can do the following:
|
||||
|
||||
```aiken
|
||||
// some_data : Data
|
||||
assert my_datum : MyDatum = some_data
|
||||
```
|
|
@ -1,9 +0,0 @@
|
|||
# Resources
|
||||
|
||||
Below is a list of links to resources we used while building Aiken.
|
||||
|
||||
- [The Gleam's compiler](https://github.com/gleam-lang/gleam);
|
||||
- [The Official Plutus documentation](https://plutus.readthedocs.io/en/latest/);
|
||||
- [The source code about encoding/Decoding UPLC](https://github.com/input-output-hk/plutus/blob/9538fc9829426b2ecb0628d352e2d7af96ec8204/plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Core/Instance/Flat.hs);
|
||||
- [The source code about core types](https://github.com/input-output-hk/plutus/blob/9538fc9829426b2ecb0628d352e2d7af96ec8204/plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Core/Type.hs);
|
||||
- [The original (albeit outdated) Plutus Core specification](https://hydra.iohk.io/build/14133599/download/1/plutus-core-specification.pdf)
|
|
@ -1,23 +0,0 @@
|
|||
# Untyped Plutus Core
|
||||
|
||||
One key feature of Aiken is how it helps you manipulate Untyped Plutus Core
|
||||
(abbrev. UPLC in short). UPLC is ultimately the format whereby codes gets
|
||||
executed on-chain. This is pretty-much as low-level as you can get when it
|
||||
comes to Cardano smart-contracts.
|
||||
|
||||
Understanding how UPLC works, and having the right tools to troubleshoot
|
||||
UPLC programs can be handy when developing contracts on Cardano. Fortunately,
|
||||
this is something Aiken can help you with.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> While UPLC has erased any _explicit_ notion of types; functions, variables and
|
||||
> constants are still _implicitly_ typed and, an interpreter will raise errors
|
||||
> when encountering a type mismatch.
|
||||
>
|
||||
> For the sake of simplicity, we might speak about the type-signature of
|
||||
> builtin functions such as `addInteger` which, in principle, only has a
|
||||
> concrete meaning in Typed Plutus Core. Hence, even though they are _untyped_,
|
||||
> we often think of UPLC programs has having implicit types, as if they were
|
||||
> originally _typed_ programs whose types had simply been erased (in fact,
|
||||
> that's exactly what they are).
|
|
@ -1,75 +0,0 @@
|
|||
# Builtins
|
||||
|
||||
| Builtins | Type Args | Term Args | Result |
|
||||
| --- | --- | --- | --- |
|
||||
| `ifThenElse`[^1] | \\(α\\) | (bool, \\(α\\), \\(α\\)) | \\(α\\) |
|
||||
| --- | --- | --- | --- |
|
||||
| `addInteger` | \- | (integer, integer) | integer |
|
||||
| `subtractInteger` | \- | (integer, integer) | integer |
|
||||
| `multiplyInteger` | \- | (integer, integer) | integer |
|
||||
| `divideInteger` | \- | (integer, integer) | integer |
|
||||
| `modInteger` | \- | (integer, integer) | integer |
|
||||
| `quotientInteger` | \- | (integer, integer) | integer |
|
||||
| `remainderInteger` | \- | (integer, integer) | integer |
|
||||
| `equalsInteger` | \- | (integer, integer) | bool |
|
||||
| `lessThanInteger` | \- | (integer, integer) | bool |
|
||||
| `lessThanEqualsInteger` | \- | (integer, integer) | bool |
|
||||
| --- | --- | --- | --- |
|
||||
| `appendString` | \- | (string, string) | string |
|
||||
| `emptyString` | \- | (string) | bool |
|
||||
| `equalsString` | \- | (string, string) | bool |
|
||||
| `encodeUtf8` | \- | (string) | bytestring |
|
||||
| --- | --- | --- | --- |
|
||||
| `appendByteString` | \- | (bytestring, bytestring) | bytestring |
|
||||
| `consByteString` | \- | (integer, bytestring) | bytestring |
|
||||
| `indexByteString` | \- | (bytestring, integer) | integer |
|
||||
| `sliceByteString` | \- | (integer, integer, bytestring) | bytestring |
|
||||
| `lengthOfByteString` | \- | (bytestring) | integer |
|
||||
| `equalsByteString` | \- | (bytestring, bytestring) | bool |
|
||||
| `lessThanByteString` | \- | (bytestring, bytestring) | bool |
|
||||
| `lessThanEqualsByteString` | \- | (bytestring, bytestring) | bool |
|
||||
| `decodeUtf8` | \- | (bytestring) | string |
|
||||
| --- | --- | --- | --- |
|
||||
| `chooseData`[^2] | \\(α\\) | (data, \\(α\\), \\(α\\), \\(α\\), \\(α\\), \\(α\\)) | \\(α\\) |
|
||||
| `constrData` | \- | (integer, list data) | data |
|
||||
| `unConstrData` | \- | data | (integer, list data) |
|
||||
| `iData` | \- | (integer) | data |
|
||||
| `unIData` | \- | (data) | integer |
|
||||
| `bData` | \- | (bytestring) | data |
|
||||
| `unBData` | \- | (data) | bytestring |
|
||||
| `mapData` | \- | (list (pair data data)) | data |
|
||||
| `unMapData` | \- | (data) | list (pair data data) |
|
||||
| `listData` | \- | (list data) | data |
|
||||
| `unListData` | \- | (data) | list data |
|
||||
| `equalsData` | \- | (data, data) | bool |
|
||||
| `serialiseData` | \- | (data) | bytestring |
|
||||
| --- | --- | --- | --- |
|
||||
| `chooseList`[^3] | \\(α, β\\) | (list \\(α\\), \\(β\\), \\(β\\)) | \\(β\\) |
|
||||
| `mkNilData` | \\(α\\) | (unit) | list \\(α\\) |
|
||||
| `mkCons` | \\(α\\) | (\\(α\\), list \\(α\\)) | list \\(α\\) |
|
||||
| `headList` | \\(α\\) | (list \\(α\\)) | \\(α\\) |
|
||||
| `tailList` | \\(α\\) | (list \\(α\\)) | list \\(α\\) |
|
||||
| `nullList` | \\(α\\) | (list \\(α\\)) | bool |
|
||||
| --- | --- | --- | --- |
|
||||
| `mkPairData` | \\(α, β\\) | (\\(α\\), \\(β\\)) | pair \\(α\\) \\(β\\) |
|
||||
| `fstPair` | \\(α, β\\) | pair \\(α\\) \\(β\\) | \\(α\\) |
|
||||
| `sndPair` | \\(α, β\\) | pair \\(α\\) \\(β\\) | \\(β\\) |
|
||||
| --- | --- | --- | --- |
|
||||
| `sha2_256` | \- | (bytestring) | bytestring |
|
||||
| `sha3_256` | \- | (bytestring) | bytestring |
|
||||
| `blake2b_256` | \- | (bytestring) | bytestring |
|
||||
| --- | --- | --- | --- |
|
||||
| `verifyEd25519Signature`[^4] | \- | (bytestring, bytestring, bytestring) | bytestring |
|
||||
| `verifyEcdsaSecp256k1Signature`[^4] | \- | (bytestring, bytestring, bytestring) | bytestring |
|
||||
| `verifySchnorrSecp256k1Signature`[^4] | \- | (bytestring, bytestring, bytestring) | bytestring |
|
||||
| --- | --- | --- | --- |
|
||||
| `error` | \\(α\\) | (unit) | \\(α\\) |
|
||||
| `trace` | \\(α\\) | (string, \\(α\\)) | \\(α\\) |
|
||||
|
||||
[^1]: Returns the second argument when the predicate is `True`, and the third argument when `False`.
|
||||
|
||||
[^2]: Each argument corresponds to each of the constructors of a builtin data (in this order): constr, map, list, integer and bytestring. The evaluation will continue with whatever branch actually corresponds to the given term value.
|
||||
|
||||
[^3]: Returns the second argument when the list is empty, and the third argument otherwise.
|
||||
|
||||
[^4]: Arguments are respectively: the public key, the message and the signature
|
|
@ -1,128 +0,0 @@
|
|||
# Command-line utilities
|
||||
|
||||
## Evaluation
|
||||
|
||||
Let's consider the following basic program:
|
||||
|
||||
<p align="right"><strong>program_1.uplc</strong></p>
|
||||
|
||||
```
|
||||
(program
|
||||
2.0.0
|
||||
[ [ (builtin addInteger) (con integer 16) ] (con integer 26) ]
|
||||
)
|
||||
```
|
||||
|
||||
We can evaluate this program using Aiken's cli via:
|
||||
|
||||
```console
|
||||
$ aiken uplc eval program_1.uplc
|
||||
```
|
||||
|
||||
```
|
||||
Result
|
||||
------
|
||||
(con integer 42)
|
||||
|
||||
Costs
|
||||
-----
|
||||
cpu: 321577
|
||||
memory: 602
|
||||
|
||||
Budget
|
||||
------
|
||||
cpu: 9999678423
|
||||
memory: 13999398
|
||||
```
|
||||
|
||||
The output indicates the result of the evaluation (`42`) as well as the
|
||||
execution cost of that program, both in terms of CPU and memory usage.
|
||||
|
||||
Note that the command also accepts arguments. So, for example, if we modify our
|
||||
program into a function that accepts an argument as follows:
|
||||
|
||||
<p align="right"><strong>program_2.uplc</strong></p>
|
||||
|
||||
```
|
||||
(program
|
||||
2.0.0
|
||||
(lam x [ [ (builtin addInteger) (con integer 16) ] x ])
|
||||
)
|
||||
```
|
||||
|
||||
You can then instrument Aiken to provide arguments upon calling the program by
|
||||
simply appending them to the `eval` command:
|
||||
|
||||
```console
|
||||
$ aiken uplc eval program_2.uplc "(con integer 26)"
|
||||
```
|
||||
|
||||
```
|
||||
Result
|
||||
------
|
||||
(con integer 42)
|
||||
```
|
||||
|
||||
## Formatting
|
||||
|
||||
Because writing UPLC by hand can be a tedious task, Aiken provides a quick way to automatically format
|
||||
a UPLC program via the `fmt` command. By default, the command override the file given as input, but you
|
||||
can also simply prints the result to stdout using `--print`. For example:
|
||||
|
||||
```console
|
||||
$ aiken uplc fmt program_2.uplc --print
|
||||
```
|
||||
|
||||
```
|
||||
(program
|
||||
2.0.0
|
||||
(lam x [ [ (builtin addInteger) (con integer 16) ] x ])
|
||||
)
|
||||
```
|
||||
|
||||
## Converting to/from bytecode
|
||||
|
||||
So far, we've been representing UPLC programs using a high-level syntax. In
|
||||
practice, however, UPLC programs are compiled into bytecode when submitted
|
||||
on-chain.
|
||||
|
||||
Aiken provides means to convert a high-level UPLC program into a low-level flat
|
||||
bytecode − and vice-versa, via the `flat` and `unflat` commands. For example:
|
||||
|
||||
```console
|
||||
$ aiken uplc flat program_1.uplc --print
|
||||
```
|
||||
|
||||
```
|
||||
00000010 00000000 00000000 00110011
|
||||
01110000 00001001 00000001 00000010
|
||||
01000000 01101001
|
||||
```
|
||||
|
||||
The `--print` flag instruments the command-line to print everything on stdout
|
||||
in a readable way. Without the flag, the command creates a file `program_1.flat`
|
||||
next to `program_1.uplc`.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> `aiken uplc flat program.uplc`
|
||||
>
|
||||
> and
|
||||
>
|
||||
> `aiken uplc flat program.uplc --out program.flat`
|
||||
>
|
||||
> are therefore equivalent.
|
||||
|
||||
From there, one can recover a UPLC high-level syntax from a flat program using
|
||||
`unflat` as such:
|
||||
|
||||
```console
|
||||
$ aiken uplc unflat program_1.flat --print
|
||||
```
|
||||
|
||||
```
|
||||
(program
|
||||
2.0.0
|
||||
[ [ (builtin addInteger) (con integer 16) ] (con integer 26) ]
|
||||
)
|
||||
```
|
|
@ -1,78 +0,0 @@
|
|||
# Syntax
|
||||
|
||||
Let's start with a little reminder about the syntax. The complete syntax for Untyped Plutus Core comes from the original [Formal Specification of the Plutus Core Language](https://hydra.iohk.io/build/14133599/download/1/plutus-core-specification.pdf).
|
||||
|
||||
## Primitive Types
|
||||
|
||||
Plutus Core has 7 primitive types (a.k.a. constants): `unit`, `bool`, `integer`, `bytestring`, `string`, `pair` and `list`.
|
||||
One can construct constants using the `con` keyword, followed by the name of the primitive type and its value.
|
||||
|
||||
- Unit is denoted `()`;
|
||||
- Bool are `True` or `False`;
|
||||
- Bytestrings are denoted with a leading `#` followed by an hexadecimal sequence;
|
||||
- Strings are UTF-8 text strings, between double quotes `"` `"`;
|
||||
- Pair and lists are encapsulated between brackets `[` and `]`.
|
||||
|
||||
Note that each constant is named after its type. For pairs and lists -- which are compound types --, the type of their elements is specified between chevrons `<` and `>`.
|
||||
|
||||
| Primitive Type | Example |
|
||||
| --- | --- |
|
||||
| `unit` | `con unit ()` |
|
||||
| `bool` | `con bool True` |
|
||||
| `integer` | `con integer 42` |
|
||||
| `bytestring` | `con bytestring #41696b656e` |
|
||||
| `string` | `con string "Aiken"` |
|
||||
| `pair` | `con pair<bool, integer> [True, 42]` |
|
||||
| `list` | `con list<bytestring> [#00, #aa]` |
|
||||
|
||||
## Functions
|
||||
|
||||
A function (or simply, lambda) is constructed with the keyword `lam` followed
|
||||
by a variable name, and a term (i.e. a constant, another function, etc..). One
|
||||
can apply variables to a function using squared brackets `[ ]`.
|
||||
|
||||
For example: `[ (lam x x) (con integer 42) ]`.
|
||||
|
||||
This little excerpt constructs a function that takes an argument `x` and returns
|
||||
it; to which we immediately apply the constant `42`. If we were to evaluate this
|
||||
program, it would simply output: `42`.
|
||||
|
||||
## Builtins
|
||||
|
||||
Plutus Core comes with a set of builtins functions which comes in handy to
|
||||
define certain operations. Incidentally, there's no _operator_ even for basic
|
||||
arithmetic operations, everything comes as a builtin.
|
||||
|
||||
You'll notice also that some builtins are very domain specific and tailored to
|
||||
operations you'd expect a smart-contract to perform on a blockchain. Hence, new
|
||||
builtins may be added in the future to address specific use cases that emerge.
|
||||
|
||||
Builtins are called with the keyword `builtin` followed by their names. They may
|
||||
take one, two, three or really any number of arguments. The complete list of builtins
|
||||
is given [in annexe](./builtins.md).
|
||||
|
||||
## Delay & Force
|
||||
|
||||
Plutus Core has the notion of type abstractions and type instantiations. That is, like lambdas are functions over term values, abstractions are functions over types. These abstractions allow to represent polymorphic types (such as, a list of elements, or an option type). UPLC has gotten rid of the types, but introduces two new keywords in order to preserve the abstractions in some form.
|
||||
|
||||
- `force` can be used on a polymorphic function to instantiate one type-parameter. For example, the branches of a builtin `ifThenElse` can be of any type -- though they have to be the same for both branches. In fact, `ifThenElse` has one type parameter. To be called, it must therefore be forced once: `[ [ [ force (builtin ifThenElse) p ] x ] y ]`
|
||||
|
||||
- Similarly, `delay` can be used to defer the evaluation of a certain term; this allows to artificially construct or preserve type abstractions, but also, to introduce a certain level of laziness in parts of the program.
|
||||
|
||||
## Data
|
||||
|
||||
In addition to primitive types, Plutus Core also has a more generalized `data`
|
||||
data-type which is meant to represent any possible data-type in a program.
|
||||
|
||||
> **TODO**: Give additional detail about how the serialization is done and how
|
||||
> to construct a Data.
|
||||
>
|
||||
> In particular, revisit after [#34](https://github.com/txpipe/aiken/issues/34)
|
||||
> since the introduction of "list" and "pair" keywords may come in handy.
|
||||
|
||||
## Programs
|
||||
|
||||
Finally, UPLC programs are wraps in a `program` declaration, which indicates
|
||||
the version (e.g. `1.0.0`) of Plutus Core that this programs uses. You don't
|
||||
have to worry about that too much. Aiken supports the latest Plutus version
|
||||
(`2.0.0`).
|
Loading…
Reference in New Issue