changed a year ago
Published Linked with GitHub
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Aiken programming language tutorial

thuc.nc@teko.vn

Note:

Speaker notes here


Aiken as a Cardano Smart Contract language


Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Cardano Smart Contract Programming language

  • Plutus: Haskell
  • plu-ts: Typescript
  • opshin: Python
  • Marlowe: blockly
  • Aiken: Rust similar

Aiken programming language

  • A new programming language and toolchain
  • Takes inspiration from: Gleam, Rust, and Elm for excellent developer experience
  • Exclusively used for creating the on-chain validator-scripts

Sample Aiken code

https://danolearn.com/en/ucode/quick-sort-59119

use aiken/list fn quicksort(xs: List<Int>) -> List<Int> { when xs is { [] -> [] [p, ..tail] -> { let before = tail |> list.filter(fn(x) { x < p }) |> quicksort let after = tail |> list.filter(fn(x) { x >= p }) |> quicksort list.concat(before, [p, ..after]) } } } test quicksort_0() { quicksort([]) == [] } test quicksort_1() { quicksort([3, 2, 1, 4]) == [1, 2, 3, 4] } test quicksort_2() { quicksort([1, 2, 3, 4]) == [1, 2, 3, 4] }

Aiken as a functional programming language


Functiontal vs non-functional

  • Functional languages: Haskell, Lisp, Clojure, Elixir, Elm, OCaml, Erlang, F#
  • Non-functional (imperative) languages: all familiar ones
  • Some modern languages combines functional programming principles: Rust, Scala, Kotlin, Swift, Dart

Functiontal vs non-functional: example

ar = [1, 2, 3]
for i in (0, len(ar)):
    ar[i] = ar[i] * 2

vs

ar = [1, 2, 3].map(v => v * 2)

Functional Programming – Characteristics

  • Designed on the concept of mathematical functions: conditional expressions & recursion
  • Supports higher-order functions and lazy evaluation
  • Do NOT support flow Controls like loop statements

Note:

  • Higher-order functions (vs first-order functions): take function(s) as arguments and/or return a function as its result
  • Lazy evaluation: the evaluation of expressions is delayed until their results are actually needed. This approach helps optimize the execution of programs by reducing the computational load, enabling efficient processing and allocation of resources.

Functional Programming – Pros & Cons

  • Pros

    • Bugs-Free Code − no states, so no side-effects
    • Efficient Parallel Programming: NO mutable states
    • Lazy Evaluation: Lazy Lists, Lazy Maps
  • Cons

    • Requires a large memory space

Note:

  • Side effects: A side effect is an interaction with the outside world: functions without returns, mutating global state inside function, missing handling cases
  • Requires a large memory space: As it does not have state, you need to create new objects every time to perform actions.

Aiken language tutorial

  • Variables & primitive types
  • Functions
  • Tests
  • Control Flow

Aiken language tutorial

  • Modules
  • Working with list
  • Working with bytearray
  • Working with dict

Variables & Primitive types

  • Variables & Constants
  • Primitive Types

Variables & Constants

let x = 1
let y: Int = x  // Type annotations
let x = 2  // shadows previous bindings

const start_year = 2001  /* module constant */
const end_year = 2011
  • Let-bindings aren't allowed in a top-level Aiken module
  • Module constant values are inlined by the compiler, like #define in C/C++.

Primitive Types

  • Int
    • Literals: 42, 14, 1_000_000
    • Operations: +, -, *, / (integer division), *, %
    • Comparasions: ==, >, <, >=, <=
  • Bool
    • Literals: True, False
    • Operations: ==, &&, ||, !

Note:

  • No floating point numbers in Aiken

Primitive Types

  • ByteArray: an array of bytes (integers ranging from 0 to 255)
    • Literals:
      • "foo"
      • #[102, 111, 111]
      • #[0x66, 0x6f, 0x6f]
      • #"666f6f"

Primitive Types

  • Tuple: grouping values, each can have a different type
    • (10, "hello") (Int, ByteArray)
    • (1, 4, [0]) (Int, Int, List<Int>)
let point = (14, 42, 1337, 0)
let a = point.1st
let b = point.2nd
let c = point.3rd
let d = point.4th
(c, d, b, a) // (1337, 0, 42, 14)

Primitive Types

  • List: ordered collections of values of the same type
let x = [2, 3]    // all data structures are immutable
let y = [1, ..x]  // inserting at the front of list x
 
x // [2, 3]
y // [1, 2, 3]

Primitive Types

  • Dict: ordered set of key-value pairs
let result =
    dict.new()
    |> dict.insert(key: "a", value: 100)
    |> dict.insert(key: "b", value: 200)
    |> dict.delete(key: "a")
    |> dict.to_pairs()

result == [Pair("b", 200)]

Functions

  • Named functions & Anonymous functions
  • Labeled Arguments
  • Pipe Operator
  • Function capturing
  • Generic functions

User-defined function

/// Document here
/// Named function, with type annotations
fn add(x: Int, y: Int) -> Int {
  x + y  // no explicit return keyword
}
    
fn run() {
  let add = fn(x, y) { x + y }  // anonymous function

  add(1, 2)
}

Labeled arguments

fn replace(self: String, pattern: String, 
    replacement: String) { 
  // ...
}
    
replace(self: @"A,B,C", pattern: @",", replacement: @" ")
 
// Labeled arguments can be given in any order
replace(pattern: @",", replacement: @" ", self: @"A,B,C")
 
// Positional arguments and labels can be mixed
replace(@"A,B,C", pattern: @",", replacement: @" ")

Pipe Operator

Pipe operator: |> - passing the result of one function to the arguments of another function.

string_builder.to_string(
    string_builder.reverse(
        string_builder.from_string(s)
    )
)
s
|> string_builder.from_string
|> string_builder.reverse
|> string_builder.to_string

Function capturing

fn add(x: Int , y: Int ) -> Int {
  x + y
}
 
fn run() {
  // This is the same as add(add(add(1, 3), 6), 9)
  // `_` indicates where the argument should be passed.
  1
  |> add(_, 3)  
  |> add(_, 6)
  |> add(_, 9)
  
  // shorthand
  1
  |> add(3)
  |> add(6)
  |> add(9)
}

Note:

Backpassing: TDB


Generic functions

  • Functions that are generic over multiple types
/// Type variable `a` is used to represent 
/// any possible type.
fn list_of_two(my_value: a) -> List<a> {
  [my_value, my_value]
}

Note:
Similar to function template in C/C++


Unit Tests with aiken

/// To write a unit test, use the `test` keyword:
test foo() {
  1 + 1 == 2  // a test returns a boolean
}

Unit Tests with aiken

https://danolearn.com/en/ucode/add-one-59120

fn add_one(n: Int) -> Int {
  n + 1
}
 
test add_one_1() {
  add_one(0) == 1
}
 
test add_one_2() {
  add_one(-42) == -41
}

Unit Tests with aiken

https://danolearn.com/en/ucode/add-one-59120
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Control Flow

  • Blocks
  • Branching with if else
  • Matching with when {expr} is
    • Basic matching
    • Destructuring

Blocks

Every block in Aiken is an expression with the value of the last expression in that block.

let value: Bool = {
    "Hello"
    42 + 12
    False
}
 
value == False

Branching with if else

fn fibonacci(n: Int) -> Int {
  if n == 0 {
    0
  } else if n == 1 {
    1
  } else {
    fibonacci(n-2) + fibonacci(n-1)
  }
}

Note:

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.


Matching with when {expr} is

when a is {
  0 -> "Zero"
  1 -> "One"
  2 -> "Two"
  n -> "Some other number" // This matches anything
}

Destructuring

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"
}

Note:
A when expr is expression can be used to destructure values that contain other values, such as tuples and lists.


Modules

  • Modules & Import
  • Some built-in modules
    • aiken/math
    • aiken/int
    • aiken/interval
    • aiken/list
    • aiken/bytearray
    • aiken/dict

Module imports

  • Module: bundle of functions and types. Modules are imported using the keyword use.
use unix/dog use animal/dog as kitty // named import
  • Unqualified import
use animal/dog.{Dog, stroke}
 
pub fn foo() {
  let puppy = Dog { name: "Zeus" }
  stroke(puppy)
}

Built-in modules

  • References: https://aiken-lang.github.io/stdlib/
  • aiken/math
    • abs(self: Int) -> Int
    • gcd(x: Int, y: Int) -> Int
    • max(a: Int, b: Int) -> Int
    • min(a: Int, b: Int) -> Int
    • pow(self: Int, e: Int) -> Int
    • sqrt(self: Int) -> Option<Int>

Practice Exercises P1

https://danolearn.com/en/course-study/blockchain-crash-course-from-fundamentals-to-dapps-development-1462?l=54427


Working with list

  • count, length, all, any
  • Indexing & Slicing
  • Searching & Sorting
  • Filter & Map
  • Reducing (Folding)

count, length

length(self: List<a>) -> Int
count(self: List<a>, predicate: fn(a) -> Bool) -> Int
list.length([]) == 0
list.length([1, 2, 3]) == 3

list.count([], fn(a) { a > 2}) == 0
list.count([1, 2, 3], fn(a) { a >= 2 }) == 2
list.count([1, 2, 3], fn(a) { a > 5 }) == 0

all, any

all(self: List<a>, predicate: fn(a) -> Bool) -> Bool
any(self: List<a>, predicate: fn(a) -> Bool) -> Bool
list.all([], fn(n) { n > 0 }) == True
list.all([1, 2, 3], fn(n) { n > 0 }) == True
list.all([1, 2, 3], fn(n) { n == 2 }) == False

Indexing

// 0-based index
at(self: List<a>, index: Int) -> Option<a>  
head(self: List<a>) -> Option<a>
last(self: List<a>) -> Option<a>
push(self: List<a>, elem: a) -> List<a>
delete(self: List<a>, elem: a) -> List<a>
use aiken/option
list.at([1, 2, 3], 1) == Some(2)
list.at([1, 2, 3], 42) == None
list.head([1, 2, 3]) == Some(1)
list.last([1, 2, 3]) == Some(3)
// insert in front
list.push([2, 3], 1) == [1, ..[2, 3]] == [1, 2, 3]  
// Remove the first occurrence of 1
list.delete([1, 2, 3, 1], 1) == [2, 3, 1]  
list.delete([1, 2, 3], 14) == [1, 2, 3]
    
option.or_else(list.at([1, 2, 3], 1), 0) == 2

Slicing

concat(left: List<a>, right: List<a>) -> List<a>
slice(self: List<a>, from: Int, to: Int) -> List<a>
init(self: List<a>) -> Option<List<a>>
tail(self: List<a>) -> Option<List<a>>
list.concat([1, 2, 3], [4, 5, 6]) 
    == [1, 2, 3, 4, 5, 6]
list.slice([1, 2, 3, 4, 5, 6], from: 2, to: 4) 
    == [3, 4, 5]
list.slice([1, 2, 3, 4, 5, 6], from: -2, to: -1) 
    == [5, 6]
list.init([1, 2, 3]) == Some([1, 2])
list.tail([1, 2, 3]) == Some([2, 3]) 

Slicing

take(self: List<a>, n: Int) -> List<a>
drop(self: List<a>, n: Int) -> List<a>
span(self: List<a>, n: Int) -> (List<a>, List<a>)
list.take([1, 2, 3], 2) == [1, 2]
list.drop([1, 2, 3], 2) == [3]  // drop first 2 items

// span(xs, n) == (take(xs, n), drop(xs, n))
span([1, 2, 3, 4, 5], 3) == ([1, 2, 3], [4, 5])

Searching

has(self: List<a>, elem: a) -> Bool
find(self: List<a>, predicate: fn(a) -> Bool) -> Option<a>
index_of(self: List<a>, elem: a) -> Option<Int>
list.has([1, 2, 3], 2) == True
list.has([1, 2, 3], 14) == False
// find first item that >= 2
list.find([1, 2, 3], fn(x) { x >= 2 }) == Some(2)  
list.find([4, 5, 6], fn(x) { x == 2 }) == None
list.index_of([1, 7, 3], 4) == None
list.index_of([1, 0, 9, 6], 6) == 3

Sorting

sort(self: List<a>, compare: fn(a, a) -> Ordering) -> List<a>
use aiken/int

sort([3, 1, 4, 0, 2], int.compare) == [0, 1, 2, 3, 4]
sort([1, 2, 3], int.compare) == [1, 2, 3]

Filtering

filter(self: List<a>, predicate: fn(a) -> Bool) 
    -> List<a>
partition(self: List<a>, predicate: fn(a) -> Bool) 
    -> (List<a>, List<a>)
drop_while(self: List<a>, predicate: fn(a) -> Bool) 
    -> List<a>
take_while(self: List<a>, predicate: fn(a) -> Bool) 
    -> List<a>
list.filter([1, 2, 3], fn(x) { x >= 2 }) == [2, 3]
list.partition([1, 2, 3, 4], fn(x) { x % 2 == 0 }) 
    == ([2, 4], [1, 3])
list.drop_while([1, 2, 3], fn(x) { x <= 2 }) == [3]
list.take_while([1, 2, 3], fn(x) { x <= 2 }) == [1, 2]

Mapping

map(self: List<a>, with: fn(a) -> result) 
    -> List<result>
map2(self: List<a>, bs: List<b>, with: fn(a, b) -> result) 
    -> List<result>
map3(
  self: List<a>, bs: List<b>, cs: List<c>,
  with: fn(a, b, c) -> result,
) -> List<result>
list.map([1, 2, 3, 4], fn(n) { n + 1 }) == [2, 3, 4, 5]
list.map2([1, 2, 3], [1, 2], fn(a, b) { a + b }) == [2, 4]
list.map3(
    [1, 2, 3], [1, 2], [1, 2, 3], 
    fn(a, b, c) { a + b + c }
) == [3, 6]

Reducing (Folding)

  • Reducing/folding: reducing the data in the list into a single value
  • Popular examples: sum, product, max, min, concat_all
  • That single value can also another list: reverse
  • The direction of the reducing makes sense: from-left-to-right vs from-right-to-left
  • Examples: https://danolearn.com/en/ucode/list-reducing-59132

Reducing (Folding)

foldl(self: List<a>, zero: b, with: fn(a, b) -> b) -> b
// (3 + (2 + (1 + 0)))
list.foldl([1, 2, 3], 0, fn(n, total) { n + total }) 
    == 6
// [3, ..[2, ..[1, ..[]]]]
list.foldl([1, 2, 3], [], fn(x, xs) { [x, ..xs] }) 
    == [3, 2, 1]

Reducing (Folding)

foldr(self: List<a>, zero: b, with: fn(a, b) -> b) -> b
// (1 + (2 + (3 + 0)))
list.foldr([1, 2, 3], 0, fn(n, total) { n + total }) 
    == 6
// [1, ..[2, ..[3, ..[]]]]
list.foldr([1, 2, 3], [], fn(x, xs) { [x, ..xs] }) 
    == [1, 2, 3]

Reducing (Folding)

reduce(self: List<a>, zero: b, with: fn(b, a) -> b) -> b
list.reduce([#[1], #[2], #[3]], #[0], bytearray.concat) 
    == #[0, 1, 2, 3]

Practice Exercises P2

https://danolearn.com/en/course-study/blockchain-crash-course-from-fundamentals-to-dapps-development-1462?l=54429


Working with bytearray & dict


Practice Exercises P3

https://danolearn.com/en/course-study/blockchain-crash-course-from-fundamentals-to-dapps-development-1462?l=54430


Aiken development toolchain


The End

thuc.nc@teko.vn


Select a repo