Ufel (Uiua form experimentation language) is a language for experementing with higher-order arrays.
In array languages, an array has a properly called its shape, which is a list of natural numbers than describes how elements are layed out along the array's axes.
In array languages like APL, J, BQN, Kap, TinyAPL, and Uiua, the shape of an array is always 1-dimensional. Ufel explores the possibility of 2-dimensional shapes, which it calls forms.
Ufel should not be used for anything other than experimentation. Features are subject to massive change.
The interpreter is may be buggy and/or crash. Many things are not implemented, and some things may be implemented incorrectly.
See primitives.md for a list of primitives.
To install the interpreter, you must build from source with Rust.
git clone https://github.com/uiua-lang/ufel
cargo install --path ufel
The Ufel file extension is .fel
.
This tutorial assumes that you have at least a basic understanding of Uiua.
Like Uiua, Ufel is a stack-based array language.
Unlike Uiua, Ufel is typed with only ASCII characters. It also runs left-to-right, rather than right-to-left.
Ufel retains Uiua's stack array notation.
[1 2 3] 4 +
# [5 6 7]
[5 8 2] [2 2 1] -
# [3 6 1]
(i)range
generates a range of numbers.
5 i
# [0 1 2 3 4]
(n)length
gets the length of an array.
[1 2 3] n
# 3
[[1 2] [3 4]] n
# 2
(r)reduce
reduces an array with a function.
[1 2 3 4] r+
# 10
(s)scan
scans an array with a function.
(h)shape
gets the shape of an array.
[1 2 3] h
# [3]
[[1 2 3] [4 5 6]] h
# [2 3]
The main thing that separates Ufel from other array languages is that the way an array's elements are laid out is not fully specified by a 1-dimensional vector shape.
Arrays in Ufel have their axes specified in a matrix. This is called the array's form.
Form is to shape as shape is to length. A form is always 2-dimensional.
You can get the form of an array with (m)form
.
[1 2 3] m
# ╭─
# ╷ 3
# ╯
[[1 2] [3 4]] m
# ╭─
# ╷ 2 2
# ╯
So far, these forms just look like normal shapes with an extra dimension. We call arrays like this normal.
Form axes are specified by their orientation, either horizontal or vertical. A normal array has a vertical rank of 1.
If we use the (~)turn
modifier, we can arrange an array vertically rather than horizontally.
~[[1 2] [3 4]] m
# ╭─
# ╷ 2
# 2
# ╯
And now a bizarre thing happens if we ask for the (n)length
or (h)shape
!
~[[1 2] [3 4]] n
# 4
~[[1 2] [3 4]] h
# [2]
This is because the array is entirely vertical, but functions operate horizontally by default.
We can tell a function to operate vertically with (~)turn
.
~[[1 2] [3 4]] ~n
# 2
~[[1 2] [3 4]] ~h
# [2 2]
Alternatively, we can use (w)swap
to swap the vertical and horizontal form axes.
~[[1 2] [3 4]] w n
# 2
The (C)chunk
function expands the axes of an array into the vertical part of its form. It takes an array and a size.
10i 5C
# ╭─
# ╷ 0 1 2 3 4
# 5 6 7 8 9
# ╯
This can be used along with the (v)fold
modifier to approximate a reshape.
The (')self
modifier duplicates the stop stack value before calling its function.
16i [4 2] vCw 'h
# ╭─
# ╷ 0 1 2 3
# ╷ 4 5 6 7
#
# 8 9 10 11
# 12 13 14 15
# ╯
# [2 2 4]
Notice that if we leave all the axes vertical instead of (w)swap
ping, modifiers like (r)reduce
will ignore those axes by default.
16i [4 2] vC r+
# 120
16i [4 2] vC ~r+
# ╭─
# ╷ 8 10 12 14
# 16 18 20 22
# ╯