FhatOS (pronounced fat-ahs) is a
distributed operating system for
ESP8266, ESP32,
Raspberry PI, and similar fabrications.
Moreover, sandboxed deployments on Linux and MacOSX systems offer the cluster large memory/storage space and processor speed.
All fHATOS resources, from individual datum, complex data structures, files, and threads exist within a single
URI address space called furi
(pronounced "fury" or "fhat URI") — a subset of the common URI space.
Programs are written in mm-ADT or C / C++
and communicate with one another via storage structures that maintain subsets of the fURI space.
In general, fHATOS provides a convenient medium for coordinating a heterogeneous collection of hardware processors and their peripheries.
FhatOS Features
-
A hardware-agnostic scheduler for executing multi-threaded monoids.
-
A memory architecture enabling the integration of various storage mediums within a single URI address space.
-
A distributed file system embedded in the URI address space.
-
A programming language for fluently creating monoids to control a distributed swarm of monads.
-
A REPL environment for writing and deploying monoids in real-time.
-
A collection common embedded systems protocols GPIO, PWM, I2C, and SPI.
-
A suite of common sensor, actuator, and UI modules.
-
A sandboxed distribution enabling Linux and MacOS systems to participate in the cluster.
-
A monoidal bootloader with support for OTA firmware updates.
FhatOS Boot Loader
The following output is from a Linux boot of FHAtOS. The purpose of this documentation is to explain the mechanics of the boot process and beyond.
$ fhatos --boot:config=../conf/boot-loader.obj
PhaseShift Studio Presents
<`--'>____ ______ __ __ ______ ______ ______ ______
/. . `' \/\ ___/\ \_\ \/\ __ \/\__ _\/\ __ \/\ ___\
(`') , @ \ __\ \ __ \ \ __ \/_/\ \/\ \ \_\ \ \___ \
`-._, / \ \_\ \ \_\ \_\ \_\ \_\ \ \_\ \ \_____\/\_____\
)-)_/-(> \/_/ \/_/\/_/\/_/\/_/ \/_/ \/_____/\/_____/
A Dogturd Stynx Production
fhatos-0.1-alpha > linux-6.8.0-54-generic > x86_64
[x86_64]
Use noobj for noobj
.oO loading system objs Oo.
[INFO] [/sys/scheduler] scheduler started
[INFO] [/sys/router] router started
[INFO] [/sys/router] main memory [total=>5760]
[INFO] [/sys/router] heap <none> spanning /sys/# mounted
[INFO] [/sys/router] heap <none> spanning /mnt/# mounted
[INFO] [/sys/router] heap /mnt/boot spanning /boot/# mounted
[INFO] [/sys/router] ../../../conf/boot_config.obj boot config file loaded (size: 813 bytes)
[INFO] [/sys/router]
[
router=>[resolve=>[namespace=>[:=>/mmadt/,fos:=>/fos/],auto_prefix=>[,/mmadt/,/mmadt/ext/,/fos/,/fos/sys/,/fos/io/,/fos/sensor/,/fos/ui/,/fos/util/,/sys/],query=>[write=>[lock=>to_do]],default_config=>[query=>[write=>[sub=>noobj]]]]]
scheduler=>[def_stack_size=>8096]
mqtt=>[broker=>mqtt://chibi.local:1883,client=>fhatos_client,async=>true,cache_size=>50]
wifi=>[ssid=>Rodkins-2G,password=>'puppymama',mdns=>fhatos]
ota=>[host=>mdns://fhatos_client:3232]
console=>[terminal=>[stdout=>/io/terminal/:stdout,stdin=>/io/terminal/:stdin],nest=>2,ellipsis=>50,prompt=>'fhatos> ',strict=>false,log=>INFO,stack_size=>24288,stack_trace=>true]
fs=>[root=>./data/fs]
]@/boot/config
[INFO] [/sys/router] router boot config dropped
[INFO] [/sys/router] scheduler boot config dropped
[INFO] [/sys/router] /sys/lib/heap type imported
[INFO] [/sys/router] /sys/lib/dsm type imported
[INFO] [/sys/router] /sys/lib/bus type imported
[INFO] [/sys/router] heap /mnt/fos spanning /fos/# mounted
.oO loading mmadt lang Oo.
[INFO] [/mnt/mmadt] query processor /mnt/mmadt/q/doc attached
[INFO] [/sys/router] heap /mnt/mmadt spanning /mmadt/# mounted
.oO loading fos models Oo.
[INFO] [/sys/router] heap /mnt/io spanning /io/# mounted
[INFO] [/sys/router] /io/parser obj loaded
[INFO] [/io/log] switching from boot logger to system logger
[INFO] [/sys/router] /io/log obj loaded
[INFO] [/sys/router] log boot config dropped
[INFO] [/sys/router] heap /mnt/cache spanning +/# mounted
[INFO] [/sys/type] /sys/structure/lib/fs/:create type defined
[INFO] [/sys/router] /io/lib/fs type imported
[INFO] [/mnt/disk] /home/killswitch/software/fhatos/build/docs/build/data/fs file system location mounted
[INFO] [/sys/router] fs /mnt/disk spanning /disk/# mounted
[INFO] [/sys/router] fs boot config dropped
[INFO] [/mnt/dsm] query processor /mnt/dsm/ attached
[INFO] [/mmadt/rec] mqtt://chibi.local:1883 mqtt fhatos_client connected
[INFO] [/sys/router] dsm /mnt/dsm spanning /shared/# mounted
[INFO] [/sys/router] mqtt boot config dropped
[INFO] [/mnt/bus] mapping /bus==>//io
[INFO] [/sys/router] bus /mnt/bus spanning /bus/# mounted
[INFO] [/sys/router] /io/console obj loaded
[INFO] [/io/console] thread spawned: inst()[cpp]
[INFO] [/sys/router] console boot config dropped
Booting on Linux/Unix/Mac
Booting on ESP32
Booting on ESP8266
Booting on RaspberryPi
FhatOS Architecture
The "animal sticker" images used throughout the documentation are of the chickens, ducks, dogs and cats that have or are currently living on the FhatFarm. To learn their names, hover on their image. |
fhatOS is designed according to the philosophy that computing is composed of 3 fundamental, interacting phenomena:
structure (space), process (time), and language (perspective).
As such,the fHatOS kernel is comprised of the followng resources:
-
/sys/scheduler
(process): coordinates all processes realized as threads, fibers, and coroutines. -
/sys/router
(structure) : manages all structures comprising a distributed, partitioned, read/write tuple space. -
/mmadt/
(language): provides parsing, type reasoning, and execution of mmADT programs.
These resources are accessible via their respective fURIs.
The fURI space is a subset of the common URI space, and is the address space through which all resources within fhatos communicate.
A fURI is dereferenced using the mmADT from
instruction (sugar’d *
).
Dereferencing returns the resources pointed to by the fURI.
In mmADT, these resources are called obj
(objects).
-
/sys/scheduler
-
/sys/router
-
/mmadt
The scheduler controls and provides access to the various processes that define the Fhatos process architecture.
fhatos> */sys/scheduler
>[
==>config=>[
===>def_stack_size=>8096
=>]
==>thread=>[
=>]
==>::=>[
===>spawn=>spawn?obj<=obj{?}(_)[cpp]
=>]
>]@/sys/scheduler
The router is responsible for storing and retrieving objs
from a pool of structures that define the FHaTOS memory architecture.
fhatos> */sys/router
>[
==>frame=>[rec][_]
==>config=>[
===>resolve=>[
====>namespace=>[
=====>:=>/mmadt/
=====>fos:=>/fos/
===>]
====>auto_prefix=>[
=====>
=====>/mmadt/
=====>/mmadt/ext/
=====>/fos/
=====>/fos/sys/
=====>/fos/io/
=====>/fos/sensor/
=====>/fos/ui/
=====>/fos/util/
=====>/sys/
===>]
====>query=>[
=====>write=>[
======>lock=>to_do
====>]
===>]
====>default_config=>[
=====>query=>[
======>write=>[sub=>noobj]
====>]
===>]
==>]
=>]
==>query=>[
===>write=>[
====>lock=>lock?obj{?}<=obj{?}()[cpp]
====>sub=>sub?obj{?}<=obj()[cpp]
==>]
=>]
==>structure=>[
===>/sys/#
===>/mnt/#
===>/boot/#
===>/fos/#
===>/mmadt/#
===>/io/#
===>+/#
===>/disk/#
===>/shared/#
===>/bus/#
=>]
>]@/sys/router
The mmADT language is embedded in the fURI address space thus enabling reflective programming.
fhatos> */mmadt/#/
>[
==>/mmadt/as=>as(type?uri=>_)[cpp]
==>/mmadt/at=>at?obj{?}<=obj{?}(isa(/mmadt/uri))[cpp]
==>/mmadt/barrier=>barrier?objs{*}<=objs{*}(_)[cpp]
==>/mmadt/bcode=>[bcode][_]
==>/mmadt/bcode/::/mmadt/inspect=>inspect(_)[cpp]
==>/mmadt/block=>block?obj<=obj{?}(_)[cpp]
==>/mmadt/bool=>[bool][_]
==>/mmadt/bool/::/mmadt/as=>as(isa(/mmadt/uri))[cpp]
==>/mmadt/bool/::/mmadt/div=>div(isa(/mmadt/bool))[cpp]
==>/mmadt/bool/::/mmadt/inspect=>inspect(_)[cpp]
==>/mmadt/bool/::/mmadt/minus=>minus(isa(/mmadt/bool))[cpp]
==>/mmadt/bool/::/mmadt/mult=>mult(isa(/mmadt/bool))[cpp]
==>/mmadt/bool/::/mmadt/neg=>neg(isa(/mmadt/bool))[cpp]
==>/mmadt/bool/::/mmadt/plus=>plus(isa(/mmadt/bool))[cpp]
==>/mmadt/chain=>chain(_)[cpp]
==>/mmadt/choose=>choose(_)[cpp]
==>/mmadt/count=>count?int<=objs{*}()[cpp]
==>/mmadt/div=>div(_)
==>/mmadt/drop=>drop?obj{?}<=obj{?}(isa(/mmadt/obj))[cpp]
==>/mmadt/each=>each(isa(/mmadt/obj))
==>/mmadt/else=>else?obj<=obj{?}(_)[cpp]
==>/mmadt/embed=>embed()[cpp]
==>/mmadt/end=>end?noobj{.}<=obj{*}()[cpp]
==>/mmadt/eq=>eq(_)[cpp]
==>/mmadt/error=>[error][_]
==>/mmadt/explain=>explain()[cpp]
==>/mmadt/ext/C=>C()[is(gte(-273.14999))]
==>/mmadt/ext/Ox=>Ox()[is(true)]
==>/mmadt/ext/char=>char()[merge(2).count().is(eq(1))]
==>/mmadt/ext/int16=>[int16][_]
==>/mmadt/ext/int32=>[int32][_]
==>/mmadt/ext/int8=>uint8()[is(gte(-127)).is(lte(128))]
==>/mmadt/ext/ms=>[real][_]
==>/mmadt/ext/ms/::/mmadt/as=>as(is(eq(/mmadt/ext/sec)))[cpp]
==>/mmadt/ext/nat=>nat()[is(gte(0))]
==>/mmadt/ext/prnt=>prnt()[is(gte(0.00000)).is(lte(100.00000))]
==>/mmadt/ext/sec=>[real][_]
==>/mmadt/ext/secret=>[str][_]
==>/mmadt/ext/secret/::/mmadt/as=>as(from(0?type,noobj)[cpp])[cpp]
==>/mmadt/ext/uint8=>uint8()[is(gte(0)).is(lte(255))]
==>/mmadt/flip=>flip(_)[cpp]
==>/mmadt/frame=>frame?rec<=obj{?}()[cpp]
==>/mmadt/from=>from?obj{?}<=obj{?}(_,else(noobj))[cpp]
==>/mmadt/gt=>gt()
==>/mmadt/gte=>gte()
==>/mmadt/inspect=>inspect(_)
==>/mmadt/inst=>[inst][_]
==>/mmadt/inst/::/mmadt/inspect=>inspect(_)[cpp]
==>/mmadt/inst/blockers=>[
===>block
===>each
===>within
===>isa
===>split
===>choose
===>chain
=>]
==>/mmadt/int=>[int][_]
==>/mmadt/int/::/mmadt/as=>as(isa(/mmadt/uri))[cpp]
==>/mmadt/int/::/mmadt/div=>div(0?int=>isa(/mmadt/int))[cpp]
==>/mmadt/int/::/mmadt/gt=>gt(_)[cpp]
==>/mmadt/int/::/mmadt/gte=>gte(_)[cpp]
==>/mmadt/int/::/mmadt/inspect=>inspect(_)[cpp]
==>/mmadt/int/::/mmadt/lt=>lt(_)[cpp]
==>/mmadt/int/::/mmadt/lte=>lte(_)[cpp]
==>/mmadt/int/::/mmadt/minus=>minus(0?int=>isa(/mmadt/int))[cpp]
==>/mmadt/int/::/mmadt/mod=>mod(isa(/mmadt/int))[cpp]
==>/mmadt/int/::/mmadt/mult=>mult(0?int=>isa(/mmadt/int))[cpp]
==>/mmadt/int/::/mmadt/neg=>neg(isa(/mmadt/int))[cpp]
==>/mmadt/int/::/mmadt/plus=>plus(0?int=>isa(/mmadt/int))[cpp]
==>/mmadt/is=>is?obj{?}<=obj(_)[cpp]
==>/mmadt/isa=>isa?obj{?}<=obj{?}(_)[cpp]
==>/mmadt/lift=>lift(_)[cpp]
==>/mmadt/lock=>lock(user=>_)[cpp]
==>/mmadt/lshift=>lshift()
==>/mmadt/lst=>[lst][_]
==>/mmadt/lst/::/mmadt/div=>div(isa(/mmadt/lst))[cpp]
==>/mmadt/lst/::/mmadt/each=>each(isa(/mmadt/lst))[cpp]
==>/mmadt/lst/::/mmadt/inspect=>inspect(_)[cpp]
==>/mmadt/lst/::/mmadt/merge=>merge?objs{*}<=lst()[cpp]
==>/mmadt/lst/::/mmadt/minus=>minus(isa(/mmadt/lst))[cpp]
==>/mmadt/lst/::/mmadt/mult=>mult(isa(/mmadt/lst))[cpp]
==>/mmadt/lst/::/mmadt/plus=>plus(isa(/mmadt/lst))[cpp]
==>/mmadt/lst/::/mmadt/split=>split(isa(/mmadt/lst))[cpp]
==>/mmadt/lst/::/mmadt/within=>within(_)[cpp]
==>/mmadt/lt=>lt()
==>/mmadt/lte=>lte()
==>/mmadt/map=>map?obj{?}<=obj{?}(_)[cpp]
==>/mmadt/merge=>merge?obj{?}<=obj()[cpp]
==>/mmadt/minus=>minus(_)
==>/mmadt/mod=>mod(from(0?rhs,noobj)[cpp])
==>/mmadt/mult=>mult(_)
==>/mmadt/neg=>neg(isa(/mmadt/obj))
==>/mmadt/neq=>neq(_)[cpp]
==>/mmadt/noobj=>[noobj][_]
==>/mmadt/not=>not?obj{?}<=obj(_)[cpp]
==>/mmadt/obj=>[obj][_]
==>/mmadt/objs=>[objs][_]
==>/mmadt/plus=>plus(_)
==>/mmadt/print=>print?obj{?}<=obj{?}(_)[cpp]
==>/mmadt/prod=>prod?obj<=objs{*}()[cpp]
==>/mmadt/real=>[real][_]
==>/mmadt/real/::/mmadt/as=>as(isa(/mmadt/uri))[cpp]
==>/mmadt/real/::/mmadt/div=>div(isa(/mmadt/real))[cpp]
==>/mmadt/real/::/mmadt/gt=>gt(_)[cpp]
==>/mmadt/real/::/mmadt/gte=>gte(_)[cpp]
==>/mmadt/real/::/mmadt/inspect=>inspect(_)[cpp]
==>/mmadt/real/::/mmadt/lt=>lt(_)[cpp]
==>/mmadt/real/::/mmadt/lte=>lte(_)[cpp]
==>/mmadt/real/::/mmadt/minus=>minus(isa(/mmadt/real))[cpp]
==>/mmadt/real/::/mmadt/mult=>mult(isa(/mmadt/real))[cpp]
==>/mmadt/real/::/mmadt/neg=>neg(isa(/mmadt/real))[cpp]
==>/mmadt/real/::/mmadt/plus=>plus(isa(/mmadt/real))[cpp]
==>/mmadt/rec=>[rec][_]
==>/mmadt/rec/::/mmadt/div=>div(isa(/mmadt/rec))[cpp]
==>/mmadt/rec/::/mmadt/each=>each(isa(/mmadt/rec))[cpp]
==>/mmadt/rec/::/mmadt/inspect=>inspect(_)[cpp]
==>/mmadt/rec/::/mmadt/lshift=>lshift(isa(/mmadt/int))[cpp]
==>/mmadt/rec/::/mmadt/merge=>merge?objs{*}<=rec()[cpp]
==>/mmadt/rec/::/mmadt/minus=>minus(isa(/mmadt/rec))[cpp]
==>/mmadt/rec/::/mmadt/mult=>mult(isa(/mmadt/rec))[cpp]
==>/mmadt/rec/::/mmadt/plus=>plus(isa(/mmadt/rec))[cpp]
==>/mmadt/rec/::/mmadt/rshift=>rshift(isa(/mmadt/uri))[cpp]
==>/mmadt/rec/::/mmadt/within=>within(_)[cpp]
==>/mmadt/ref=>ref?obj{?}<=obj{?}(_,block(noobj))[cpp]
==>/mmadt/repeat=>repeat(from(0?code,noobj)[cpp],from(1?until,true)[cpp],from(2?emit,false)[cpp])[cpp]
==>/mmadt/rshift=>rshift()
==>/mmadt/split=>split(_)[cpp]
==>/mmadt/start=>start?objs{*}<=noobj{.}(_)[cpp]
==>/mmadt/str=>[str][_]
==>/mmadt/str/::/mmadt/as=>as(isa(/mmadt/uri))[cpp]
==>/mmadt/str/::/mmadt/div=>div(isa(/mmadt/str))[cpp]
==>/mmadt/str/::/mmadt/gt=>gt(_)[cpp]
==>/mmadt/str/::/mmadt/gte=>gte(_)[cpp]
==>/mmadt/str/::/mmadt/inspect=>inspect(_)[cpp]
==>/mmadt/str/::/mmadt/lt=>lt(_)[cpp]
==>/mmadt/str/::/mmadt/lte=>lte(_)[cpp]
==>/mmadt/str/::/mmadt/merge=>merge?objs{*}<=str()[cpp]
==>/mmadt/str/::/mmadt/minus=>minus(isa(/mmadt/str))[cpp]
==>/mmadt/str/::/mmadt/mult=>mult(isa(/mmadt/str))[cpp]
==>/mmadt/str/::/mmadt/plus=>plus(isa(/mmadt/str))[cpp]
==>/mmadt/str/::/mmadt/within=>within(_)[cpp]
==>/mmadt/sum=>sum?obj<=objs{*}()[cpp]
==>/mmadt/to=>to(_,true)[cpp]
==>/mmadt/type=>type?uri<=obj{?}(_)[cpp]
==>/mmadt/uri=>[uri][_]
==>/mmadt/uri/::/mmadt/as=>as(isa(/mmadt/uri))[cpp]
==>/mmadt/uri/::/mmadt/div=>div(isa(/mmadt/uri))[cpp]
==>/mmadt/uri/::/mmadt/gt=>gt(_)[cpp]
==>/mmadt/uri/::/mmadt/gte=>gte(_)[cpp]
==>/mmadt/uri/::/mmadt/inspect=>inspect(_)[cpp]
==>/mmadt/uri/::/mmadt/lshift=>lshift(_)[cpp]
==>/mmadt/uri/::/mmadt/lt=>lt(_)[cpp]
==>/mmadt/uri/::/mmadt/lte=>lte(_)[cpp]
==>/mmadt/uri/::/mmadt/merge=>merge?objs{*}<=uri()[cpp]
==>/mmadt/uri/::/mmadt/minus=>minus(isa(/mmadt/uri))[cpp]
==>/mmadt/uri/::/mmadt/mult=>mult(isa(/mmadt/uri))[cpp]
==>/mmadt/uri/::/mmadt/plus=>plus(isa(/mmadt/uri))[cpp]
==>/mmadt/uri/::/mmadt/rshift=>rshift(_)[cpp]
==>/mmadt/within=>within(_)
>]
The [cpp] representation of an inst value means that the instruction’s implementation is written C++.
As such, no further introspection is possible from within mmADT.
When the instruction implementation is written in mmADT, the instruction value is displayed as bcode (a linear chain of objs ).
|
An
|
An
|
A fhATOs instance is shutdown by writing noobj
(null) to every fURI address.
fhatos> # -> noobj
[ERROR] [/sys/router] # crosses multiple structures
[INFO] [/sys/router] 1 bus(s) closing
[INFO] [/sys/router] 1 dsm(s) closing
[INFO] [/sys/router] 1 fs(s) closing
[INFO] [/sys/router] 7 heap(s) closing
[INFO] [/mmadt/rec] disconnecting from [mqtt://chibi.local:1883]
[INFO] [/sys/router] router /sys/router stopped
[INFO] [/sys/router] /sys/# heap detached
[INFO] [/sys/router] /mnt/# heap detached
[INFO] [/sys/router] /boot/# heap detached
[INFO] [/sys/router] /fos/# heap detached
[INFO] [/sys/router] /mmadt/# heap detached
[INFO] [/sys/router] /io/# heap detached
[INFO] [/sys/router] +/# heap detached
[INFO] [/sys/router] /disk/# fs detached
[INFO] [/sys/router] /shared/# dsm detached
[INFO] [/sys/router] /bus/# bus detached
This documentation will explore these three kernel resources in-depth starting with the mmADT language and processor.
The mm-ADT Language
mmADT is the programming language of FHaTOs. In mmADT, every expression is an
obj
(object). The language has an
underlying monoidal structure where an obj
can be applied (.
) to an obj
to create an obj
.
⠀⠀
An obj
is composed of a type, a value, a variable frame, and a storage location/reference.
The abstract syntax of a sugar-free obj
is
-
The type is a fURI referring to an
obj
which determines whether theobj
is of that type or not (predicate). -
The frame is a collection of fURI referenced
objs
that are accessible to the value of theobj
(arguments). -
The value is a collection of
objs
denoting the form of theobj
(encoding). -
The reference is a fURI denoting the durable location of the
obj
with the underlying storage structure (memory address).
The mmADT language and its evaluation by a processor will be explained via an exploration of these substructures, where finer grained structures lie within each.
The Type
There are 9 base types in mmADT. 6 mono-types and 3 poly-types. The mono-types are:
-
/mmadt/noobj
: A singleton representingnull
. -
/mmadt/bool
: The set of binary valuestrue
andfalse
. -
/mmadt/int
: The set of \$n\$-bit integers between \$-2^(n-1)\$ and \$2^(n-1)\$. -
/mmadt/real
: The set of \$n\$-bit floating point values between-…
and….
. -
/mmadt/str
: The infinite set of all UTF-8 character sequences. -
/mmadt/uri
: The infinite set of all fHATos UTF-8 Uniform Resource Identifiers (fURIs).
The poly-types are:
-
/mmadt/lst
: An (un)ordered collection of zero or moreobjs
. -
/mmadt/rec
: An (un)ordered collection of key/value pairobjs
, where keys are unique.
All other types are defined in terms of these types. Every obj
has an explicitly declared type.
However, given frequency of base types usage, specifying the type is not necessary it can be deduced from the value.
-
bool
-
int
-
real
-
str
-
uri
-
lst
-
rec
-
noobj
fhatos> /mmadt/bool[true]
==>true
fhatos> bool[true]
==>true
fhatos> true
==>true
fhatos> /mmadt/int[6]
==>6
fhatos> int[6]
==>6
fhatos> 6
==>6
fhatos> /mmadt/real[6.2]
==>6.200000
fhatos> real[6.2]
==>6.200000
fhatos> 6.2
==>6.200000
fhatos> /mmadt/str['cooties']
==>'cooties'
fhatos> str['cooties']
==>'cooties'
fhatos> 'cooties'
==>'cooties'
fhatos> /mmadt/uri[/dog/curly]
==>/dog/curly
fhatos> uri[/dog/curly]
==>/dog/curly
fhatos> /dog/curly
==>/dog/curly
fhatos> /mmadt/lst[['a',2,true]]
==>['a',2,true]
fhatos> lst[['a',2,true]]
==>['a',2,true]
fhatos> ['a',2,true]
==>['a',2,true]
fhatos> /mmadt/rec[[a=>6,b=>false]]
==>[a=>6,b=>false]
fhatos> rec[[a=>6,b=>false]]
==>[a=>6,b=>false]
fhatos> [a=>6,b=>false]
==>[a=>6,b=>false]
fhatos> /mmadt/noobj[]
fhatos> noobj[]
fhatos> noobj
fhatos>
fhatos>
When an mmADT obj
is wrapped in a type[]
-bracket, the type fURI is first resolved to it’s obj
form (typically as an inst
) and then the wrapped obj
is applied to it.
If the result of the application yields an error
or a noobj
, then the base value obj
is not of that type and a type error
is thrown.
However, should any other obj
be returned, then the base value obj
is of that type and is returned wrapped in the respective type[]
-bracket.
The type can be understood as a predicate, where an error
or noobj
is false
, otherwise true
.
Finally, if the obj
has a @
-reference, then any subsequent mutations to that obj
must continue to satisfy the constraints of the type.
If any mutation falls outside the bounds of the type, a type error
is thrown.
The @
-reference ensures that as the referenced obj
mutates, it’s corresponding representation in the underlying fURI structure mutates as well.
This captures the notion of pass-by-reference vs. pass-by-value.
The mechanics of obj
typing are exemplified below using the generally useful types provided by the /mmadt/ext
prefix.
fhatos> */mmadt/ext/#/
>[
==>/mmadt/ext/C=>C()[is(gte(-273.14999))]
==>/mmadt/ext/Ox=>Ox()[is(true)]
==>/mmadt/ext/char=>char()[merge(2).count().is(eq(1))]
==>/mmadt/ext/int16=>[int16][_]
==>/mmadt/ext/int32=>[int32][_]
==>/mmadt/ext/int8=>uint8()[is(gte(-127)).is(lte(128))]
==>/mmadt/ext/ms=>[real][_]
==>/mmadt/ext/ms/::/mmadt/as=>as(is(eq(/mmadt/ext/sec)))[cpp]
==>/mmadt/ext/nat=>nat()[is(gte(0))]
==>/mmadt/ext/prnt=>prnt()[is(gte(0.00000)).is(lte(100.00000))]
==>/mmadt/ext/sec=>[real][_]
==>/mmadt/ext/secret=>[str][_]
==>/mmadt/ext/secret/::/mmadt/as=>as(from(0?type,noobj)[cpp])[cpp]
==>/mmadt/ext/uint8=>uint8()[is(gte(0)).is(lte(255))]
>]
-
char
-
nat
-
celsius
A char is a str
containing a single character.
fhatos> *char
==>char?int<=str()
[merge(2).count().is(eq(1))]
fhatos> char['a']@a
==>char['a']@a
fhatos> char['b']@b
==>char['b']@b
fhatos> @a + @b
[ERROR] [/sys/scheduler] [/mmadt/noobj] noobj accessed as str
thrown at inst char['a']@a => plus()[cpp] [0=>'@a + @b',code=>'@a + @b']
fhatos> *a
==>char['a']@a
fhatos> @a.as(str) + @b
==>'aa'@a
A natural number is an element of the set \(\mathbb{N} = \{0,1,2,\ldots,\infty\}\).
fhatos> *nat
==>nat?int<=int()[is(gte(0))]
fhatos> nat[12]
==>nat[12]
fhatos> nat[-30]
[ERROR] [/sys/scheduler] [/sys/lang/parser] -30 is not a /mmadt/ext/nat as defined by nat()[is(gte(0))]
fhatos> nat[12]@a
==>nat[12]@a
fhatos> @a.minus(11)
==>nat[1]@a
fhatos> @a.minus(2)
[ERROR] [/sys/scheduler] [/sys/lang/parser] -1@a is not a /mmadt/ext/nat as defined by nat()[is(gte(0))]
thrown at inst nat[1]@a => minus(0?int=>2)[cpp] [0=>'@a.minus(2)',0?int=>2]
fhatos> *a
==>nat[1]@a
Celsius is a temperature metric ranging from absolute zero (-273.15°) to infinity.
fhatos> *C
==>C?real<=real()[is(gte(-273.14999))]
fhatos> C[0.0]
==>C[0.000000]
fhatos> C[274.0]
==>C[274.000000]
fhatos> C[-274.0]
[ERROR] [/sys/scheduler] [/sys/lang/parser] -274.00000 is not a /mmadt/ext/C as defined by C()[is(gte(-273.14999))]
The Frame
The frame of an obj
is a set of fURI named variables that are dereferenceably accessible within the value of the obj
.
If an obj
has a specified frame, the obj
is called an inst
(instruction).
An inst
is a function
where \(\tt{obj}_\tt{in}\) is the left hand side obj
(input) and \(a_m\) are the variables of the obj
frame (arguments).
To demonstrate how frames work, the inst
band
is defined. This function takes two int
arguments. If the incoming
obj
is within the bounds of the two ints
, it is emitted, else noobj
is returned. The arguments are stored in an
inst
-specific frame mounted in the router. When the inst
completes it’s execution, the frame is unmounted. If an inst
calls another inst
, then a stack of frames is realized and arguments declared in the parent inst
are accessible
in the child inst
.
-
positional args
-
named args
-
default args
-
typed args
-
contextual args
-
dependent args
-
refined type
An inst
frame is defined by the inst
arguments.
These arguments can be accessed within the inst
via their lst
position as a fURI.
For example, <0>
references the first argument, <1>
the second argument, so on and so forth.
fhatos> band -> ||band?int{?}<=int(_,_)[is(gte(*<0>)).is(lte(*<1>))]
==>band?int{?}<=int(_,_)
[band?int{?}<=int[is(gte(from(0))).is(lte(from(1)))]]
fhatos> 3.band(2,8) (1)
[ERROR] [/sys/scheduler] [/mmadt/bcode] from(0) accessed as int
thrown at inst 3 => gte(from(0)) [0=>'3.band(2,8) // <1>',1=>8]
thrown at inst 3 => band?int{?}<=int(2,8)[band?int{?}<=int[is(gte(from(0))).is(lte(from(1)))]] [0=>'3.band(2,8) // <1>',1=>8]
fhatos> 10.band(2,8) (3)
[ERROR] [/sys/scheduler] [/mmadt/bcode] from(0) accessed as int
thrown at inst 10 => gte(from(0)) [0=>'10.band(2,8) // <3>',1=>8]
thrown at inst 10 => band?int{?}<=int(2,8)[band?int{?}<=int[is(gte(from(0))).is(lte(from(1)))]] [0=>'10.band(2,8) // <3>',1=>8]
1 | … |
2 | … |
A named argument denotes a fURI that references an obj
in the router’s frame structure.
fhatos> band -> ||band?int{?}<=int(min=>_,max=>_)[is(gte(*min)).is(lte(*max))]
==>band?int{?}<=int(min=>_,max=>_)
[band?int{?}<=int[is(gte(from(min))).is(lte(from(max)))]]
fhatos> 3.band(2,8) (1)
==>3
fhatos> 3.band(max=>8,min=>2) (2)
==>3
fhatos> 10.band(2,max=>8) (3)
==>band
fhatos> 'abc'.band(2,8) (4)
[ERROR] [/sys/scheduler] [/mmadt/int] 2 accessed as str
thrown at inst 'abc' => gte(from(min)) [0=>'\'abc\'.band(2,8) // <4>',1=>8,min=>2,max=>8]
thrown at inst 'abc' => band?int{?}<=int(2,8,min=>2,max=>8)[band?int{?}<=int[is(gte(from(min))).is(lte(from(max)))]] [0=>'\'abc\'.band(2,8) // <4>',1=>8,min=>2,max=>8]
1 | If the argument name isn’t provided, then it’s position in the argument list determines its nane, |
2 | When arguments are named, they can be written in any order. |
3 | Named arguments and unnamed arguments can be used together. |
4 | The domain of band is int . Note where the error is thrown when str['abc'] is provided. |
If the incoming obj
to else
is noobj
, the else
emits it’s argument, else it emits the incoming obj
.
This makes else
useful for expressing default arguments.
fhatos> band -> ||band?int{?}<=int(min=>else(2),max=>else(8))[is(gte(*min)).is(lte(*max))]
==>band?int{?}<=int(min=>else?noobj<=obj(2)[noobj],max=>else?noobj<=obj(8)[noobj])
[band?int{?}<=int[is(gte(from(min))).is(lte(from(max)))]]
fhatos> 1.band(min=>1) (1)
==>1
fhatos> 10.band() (2)
==>10
fhatos> {2,3,4,5}.band(min=>3,max=>4) (3)
[ERROR] [/sys/scheduler] [/mmadt/obj] /mmadt/lte?dom=/mmadt/obj&dc=1,1&rng=/mmadt/bool&rc=1,1 inst unresolved
lhs id inst id resolve obj
->[/mmadt/obj] /mmadt/lte => lte()
thrown at inst 2 => band?int{?}<=int(min=>3,max=>4)[band?int{?}<=int[is(gte(from(min))).is(lte(from(max)))]] [0=>'{2,3,4,5}.band(min=>3,max=>4) // <3>',min=>3,max=>4]
1 | No max is provided, so the default value of 8 is used. |
2 | No min nor max is provided, so the defaults 2 and 8 , respectively are used. |
3 | When arguments are provided, the default values are not used. |
If an argument is defined with a query type, then the argument’s value must satisfy that type’s specification. This is a consequence of attaching a type query processor to the router frame structure and thus, is analogous to type specifications in other structures.
fhatos> is_divisible -> ||is_divisble?bool<=int(by?int=>_)[mod(*by).eq(0)]
==>is_divisble?bool<=int(by?int=>_)
[is_divisble[mod(from(by)).eq(0)]]
fhatos> 10.is_divisible(2)
==>true
fhatos> 10.is_divisible('abc')
[ERROR] [/sys/scheduler] [/sys/lang/parser] 'abc' is not a int as defined by [int][_]
\_/mmadt/str is not a subtype of /mmadt/int
fhatos> is_divisible -> ||is_divisble?bool<=int(by?int=>_)[mod(*by).eq(0)]
==>is_divisble?bool<=int(by?int=>_)
[is_divisble[mod(from(by)).eq(0)]]
fhatos> 10.is_divisible(2)
==>true
fhatos> 10.is_divisible('abc')
[ERROR] [/sys/scheduler] [/sys/lang/parser] 'abc' is not a int as defined by [int][_]
\_/mmadt/str is not a subtype of /mmadt/int
If an argument’s type is dependent on the value of another argument, then the argument is a dependent argument.
fhatos> band -> ||band?int{?}<=int(min=>_,max=>is(gt(*min)).else(*min.plus(1)))[is(gte(*min)).is(lte(*max))]
==>band?int{?}<=int(min=>_,max=>is?noobj<=obj(gt?noobj<=obj(from?noobj<=obj(min)[noobj])[noobj])[noobj].else?noobj<=obj(from?noobj<=obj(min)[noobj].plus?noobj<=obj(1)[noobj])[noobj...
[band?int{?}<=int[is(gte(from(min))).is(lte(from(max)))]]
fhatos> 2.band(min=>1,max=>1) (1)
==>2
1 | … |
fhatos> is_divisible -> ||is_divisble?bool<=int(by?int=>is(neq(0)).else(print('error').map(1)))[mod(*by).eq(0)]
==>is_divisble?bool<=int(by?int=>is?noobj<=obj(neq?noobj<=obj(0)[noobj])[noobj].else?noobj<=obj(print?noobj<=obj('error')[noobj].map?noobj<=...
[is_divisble[mod(from(by)).eq(0)]]
fhatos> 10.is_divisible(2)
error==>true
fhatos> 10.is_divisible('abc')
error[ERROR] [/sys/scheduler] [/sys/lang/parser] 'abc' is not a int as defined by [int][_]
\_/mmadt/str is not a subtype of /mmadt/int
For instance:
fhatos> int(a=>2)[*a]
==>2
fhatos> 4 + int(a=>2)[*a]
==>6
fhatos> 4 + int(a=>2)[+*a]
==>10
The Value
The value of an obj
is the datum specifying the instance aspects of the obj
within the boundaries of the type aspects of the obj
.
The Reference
The reference of an obj
is a fURI denoting the location of the obj
within the underlying fURI addressable structure.
The FhaTOS structure is the storage medium of all persistent objs
.
If an obj
does not have a reference, then the obj
is transient — existing only within the data flow.
When an obj
has a reference, the obj
encoding in the data flow (hardware main memory) and within the structure (FHAtOs persistence) are synchronized.
-
from *
-
at @
-
pubsub ?sub
|
The fURI
\[
\begin{align*}
*\tt{y} & \rightarrow \tt{z} \\
*\tt{z} & \rightarrow 12 \\
**\tt{y} & \rightarrow 12
\end{align*}
\]
|
|
|
|
subscribes to |
-
memory
-
thread
fhatos> a -> 'axel'
==>'axel'
fhatos> *a
==>'axel'
fhatos> *a + ' fantaxel'
==>'axel fantaxel'
fhatos> *a
==>'axel'
fhatos> @a + ' fantaxel'
==>'axel fantaxel'@a
fhatos> *a
==>'axel fantaxel'@a
fhatos> thread[[
loop=>from(a,0).plus(1).to(a).is(gt(10)).true.to(/abc/halt),
halt=>false,
delay=>nat[0]]]@abc
>thread[
==>loop=>from(a,0).plus(1).to(a).is(gt(10)).map(true).to(/abc/halt)
==>halt=>false
==>delay=>nat[0]
>]@abc
The bit-length of int and real can be specified at boot time via the boot-loader.
Other machines in the cluster with a different bit-length encodings can still be communicated with.
However, overflow is possible, but can be automatically checked using types in /mmadt/ext/ such as:
int8 , int16 , int32 .
|
|
|
Functional Types
The wildcard feature of the fURI scheme makes it possible to access instructions associated with a particular type.
fhatos> */mmadt/int/#
==>[int][[_]]
==>as?obj<=int(isa?noobj<=obj(/mmadt/uri)[noobj])[cpp]
==>div?int<=int(0?int=>isa?noobj<=obj(/mmadt/int)[noobj])[cpp]
==>gt?bool<=int(_)[cpp]
==>gte?bool<=int(_)[cpp]
==>inspect?rec<=int(_)[cpp]
==>lt?bool<=int(_)[cpp]
==>lte?bool<=int(_)[cpp]
==>minus?int<=int(0?int=>isa?noobj<=obj(/mmadt/int)[noobj])[cpp]
==>mod?int<=int(isa?noobj<=obj(/mmadt/int)[noobj])[cpp]
==>mult?int<=int(0?int=>isa?noobj<=obj(/mmadt/int)[noobj])[cpp]
==>neg?int<=int(isa?noobj<=obj(/mmadt/int)[noobj])[cpp]
==>plus?int<=int(0?int=>isa?noobj<=obj(/mmadt/int)[noobj])[cpp]
Sugar-Less mm-ADT
In the code example above, the expression to import
/mmadt/ext
is pretty intense looking, to say the least.
/sys/router/config/resolve/auto_prefix -> *(_) + \|[/mmadt/ext/]
The line above looks daunting because it contains numerous syntactic sugars.
Specifically, the binary and unary operators →
(binary), *
(unary), (unary),
+
(binary), and \|
(unary).
Each of these symbols ultimately parse down to an inst
.
Each having that familiar functional form of f(a,b,c,…)
.
For example, the _sugar free representation of the expression above is:
|
|
Given that uri[/sys/router/config/resolve/auto_prefix]
resolves to a lst
of uris
,
uri[/mmadt/ext]
is added that that lst
and the updated lst
is written back to
uri[/sys/router/config/resolve/auto_prefix]
.
The one instruction that was not discussed above is block
(sugar’d |
).
This is perhaps the most useful instruction in the whole of mm-ADT and knowing how to uses is absoluately crucial to being competent with the language.
Moreover, when block
is understood, so is a large portion of the language understood as well.
Before diving into block
, it’s important to first realize how instructions are evaluated.
For this, the fundamental, immutable instruction apply
(sugar’d .
) is the perfect place to start.
Inst Evaluation Mechanics
An mm-ADT inst
is an instruction.
More generally, a function.
More abstractly, a function.
Syntactically, an inst
has the form:
Starting with the template above, components will be removed to highlight various inst
forms and functions.
-
\(\tt{type}(\tt{frame})[\tt{value}]@\tt{ref}\): The complete form is a referenced
inst
and is used with coroutines. -
\(\tt{type}(\tt{frame})[\tt{value}]\): Without a reference location, the
obj
is a standardinst
. -
\(\tt{type}(\tt{frame})[]\): Without a reference or value, the
obj
is a protoinst
resolved to a standardinst
during compilation or runtime. -
\(\tt{type}()[]\): Without a reference, value, or frame, the
obj
is a zero-arg protoinst
and is resolved during compilation or runtime. -
\(\tt{type}\): Without a reference, value, frame, or respective tokens, the
obj
is aninst
reference which can be dereferenced to yield the correspondinginst
implementation.
type?rng{coeff}<=dom{coeff}(arg1, arg2, ...) [bcode]
The fURI query type-specification is more advanced and requires an understanding of structure query processors.
As such, for now, realize an inst
to have the form:
type(arg1, arg2, ...) [bcode]
In order to evaluate an inst
an obj
must be applied to it.
Application is sugar’d .
.
inst(arg1, arg2, ...)
obj_d.inst(arg1, arg2, ...)
inst(arg1, arg2, ...) => obj_r
When an obj
is applied to an inst
, the obj
is called the left-hand side obj
.
This obj
is the catalyst for a cascade of events that take place across the inst
arguments and internal bcode
.
The sequence of events are diagrammatically represented in the graphical explanation below where each line is a new timestep in the process.
|
|
1 | The inst with a collection of arguments and a bcode body called inst_f. |
2 | A left-hand side obj is applied to the inst . |
3 | The left-hand side obj is split across all arguments and applied to each. |
4 | When all argument applications have completed, the left-hand side obj percolates through the bcode . |
5 | The right-hand side obj produced by the bcode is the result of the application. |
6 | The right-hand side obj becomes the input to the next inst in the large bcode expression (not shown). |
The diagram states that the input obj
is applied to each argument, the result of which are the actual arguments provided to the inst
.
The inst
is thus, generally defined as:
What separates inst
from other poly
types such as lst
and rec
(discussed next) is that it mounts a thread-local structure on the router called a fos:frame
.
The router supports a chain fos:frame
structures and, in this way, fos:frame
serves the purpose of a callstack, where the arguments of the inst
can be dereferenced within the body of the inst
.
fhatos> 34.make_bigger(a=>plus(10))[plus(*a)]
==>88
In the example above, make_bigger
is defined "on the fly" (a "named lambda", if one chooses to see it as such) where the argument a
can be dereferenced within the body of the inst
[ … ]
.
The input to the body of the inst
is, as can be expected, the left-hand side int[34]
.
Generalized Poly Evaluation Mechanics
The
fos:frame
is the only aspect of an inst
that makes it unique because every poly
-type supports the same internally recursive application of an left-hand side obj
.
For example, see how the internal objs
if a lst
are effected by the application of an obj
outside of the lst
.
Lst Application
fhatos> 2.lst[[1,plus(2),mult(plus(3)),'a']]
>[
==>1
==>4
==>10
==>'a'
>]
Note that the application is recursive.
For example, 2.mult(plus(3))
is evaluated as follows:
Rec Application
A rec
behaves in a similar manner to lst
and inst
when a left-hand side obj
is applied to it.
However, what makes
rec
interesting and useful beyond a data storage structure is it’s delayed evaluation semantics denoted by ⇒
.
fhatos> 2.rec[[is(gt(2)) => plus(2), _ => 0]]
[ERROR] [/sys/scheduler] [/mmadt/obj] /mmadt/gt?dom=/mmadt/obj&dc=1,1&rng=/mmadt/bool&rc=1,1 inst unresolved
lhs id inst id resolve obj
->[/mmadt/obj] /mmadt/gt => gt()
This feature of rec
make it both a data structure and a flow control structure as once an obj
has been applied to rec
, the values of rec
can be "drained".
For instance, if
is implemented with a two entry rec
, where one entry maps to noobj
.
fhatos> /io/console/config/nest -> 0 (1)
==>0
fhatos> {1,2,3}.[is(gt(2)) => _, _ => noobj] (2)
[ERROR] [/sys/scheduler] [/mmadt/obj] /mmadt/gt?dom=/mmadt/obj&dc=1,1&rng=/mmadt/bool&rc=1,1 inst unresolved
lhs id inst id resolve obj
->[/mmadt/obj] /mmadt/gt => gt()
fhatos> {1,2,3}.[is(gt(2)) => _, _ => noobj]>- (3)
[ERROR] [/sys/scheduler] [/mmadt/obj] /mmadt/gt?dom=/mmadt/obj&dc=1,1&rng=/mmadt/bool&rc=1,1 inst unresolved
lhs id inst id resolve obj
->[/mmadt/obj] /mmadt/gt => gt()
1 | Reducing the console’s display depth for nested structures (purely aesthetic). |
2 | A stream of objs is applied one-by-one to the rec yielding a new internally-applied rec . |
3 | The internally-applied rec is "drained" via the merge inst (sugar’d >- ). |
In the above example, since 1
and 2
were mapped to noobj
, they are effectively removed from the execution pipeline.
However, because 3
is gt(2)
, it is mapped to _
(its self).
Thus, when >-
is applied to this rec
, the result is
{noobj,noobj,3}
which is equivalent to {3}
.
In this way, rec
is both a data structure and a flow control structure.
It’s not difficult to realize how an "if"-rec
generalizes to support the various plays on one of computing’s most important concepts: the branch.
-
if-else
-
switch
-
guard
-
pattern
-
hash
fhatos> {1,2,3}-|[
is(gt(2)) => mult(-1), (1)
_ => mult(100)] (2)
==>1
==>2
==>3
fhatos> {1,2,3}-|[
is(gt(2)) => mult(-1),
_ => mult(100)]>-
==>100
==>200
==>-3
1 | The if branch. |
2 | The else branch. |
fhatos> {1,2,3}-<[
is(gt(0)) => mult(-1),
is(gt(1)) => mult(0),
is(gt(2)) => _]
>[
==>is(gt(0))=>-1
>]
>[
==>is(gt(0))=>-2
==>is(gt(1))=>0
>]
>[
==>is(gt(0))=>-3
==>is(gt(1))=>0
==>is(gt(2))=>3
>]
fhatos> {1,2,3}-<[
is(gt(0)) => mult(-1),
is(gt(1)) => mult(0),
is(gt(2)) => _]>-
==>-1
==>-2
==>0
==>-3
==>0
==>3
fhatos> {1,2,3}.[
==>1
==>2
==>3
fhatos> --- todo
The merge (sugar’d >- ) instruction has a correlate: split (sugar’d -< ).
The way to think of these two instructions is that they either branch a serial execution pipeline (split ) or the join a collection of parallel executing pipelines (merge ).
Interestingly, the application of an obj to a poly implements the split instruction.
So why does an explicit split instruction exist?
Because there are other ways in which branching pipelines can be defined and evaluated.
This will be discussed later when discussing fos:thread , fos:coroutine , and fos:fiber .
|
Obj Application
The universal application of .
(apply) implies that every obj
is a function as every obj
can have another obj
applied to it.
This is, in fact, the case.
fhatos> 1.plus(1) (1)
==>2
fhatos> 1. 2 (2)
==>2
fhatos> 1.2.2 (3)
==>2
fhatos> [1,2,3].<1> (4)
==>2
fhatos> [a=>1,b=>2].b (5)
==>2
1 | int[1] applied to inst[plus(1)] . |
2 | int[1] applied to int[2] (the space before . is necessary to avoid parsing as a real ). |
3 | real[1.2] applied to int[2] . |
4 | lst\[[1,2,3]] applied to the uri[1] . |
5 | rec\[[a⇒1,b⇒2]] applied to the uri[b] . |
X |
noobj |
bool |
int |
real |
str |
uri |
lst |
rec |
inst |
bcode |
noobj |
x |
y |
a |
b |
c |
d |
e |
f |
g |
|
bool |
x |
y |
z |
a |
b |
c |
d |
e |
f |
g |
int |
x |
y |
z |
a |
b |
c |
d |
e |
f |
g |
real |
x |
y |
z |
a |
b |
c |
d |
e |
f |
g |
str |
x |
y |
z |
a |
b |
c |
d |
e |
f |
g |
uri |
x |
y |
z |
a |
b |
c |
d |
e |
f |
g |
lst |
x |
y |
z |
a |
b |
c |
d |
e |
f |
g |
rec |
x |
y |
z |
a |
b |
c |
d |
e |
f |
g |
inst |
x |
y |
z |
a |
b |
c |
d |
e |
f |
g |
bcode |
x |
y |
z |
a |
b |
c |
d |
e |
f |
g |
Values
By Value vs. By Reference
|
|
fhatos> a?sub -> |print(_)
a?suba?sub==>a?sub
fhatos> a -> 12
==>12
fhatos> @a.inspect()
>[
==>type=>[
===>id=>/mmadt/int
===>obj=>[int][_]
===>dom=>[id=>/mmadt/obj,coeff=>[1,1]]
===>rng=>[id=>/mmadt/int,coeff=>[1,1]]
=>]
==>value=>[
===>id=>a
===>obj=>12
===>encoding=>int32_t
=>]
==>sub=>[
===>source=>/io/console
===>pattern=>a
===>on_recv=>a?sub
=>]
>]
fhatos> @a.plus(1)
==>13@a
fhatos> @a.plus(1).plus(1)
==>15@a
Types
Every mmADT obj
is typed.
A type is an mmADT obj
.
A obj
can serve as a value in one situation and as a type in another.
Types can be typed.
Bytecode and Instruction Types
User Defined Types
mm-ADT is a structurally typed language, whereby if an
obj
A matches obj
B, then A is a type of B.
An obj
type is a simply an mm-ADT program that verifies instances of the type.
For instance, if a natural number \(\mathbb{N}\) is any non-negative number, then natural numbers are a subset (or refinement) of int
.
fhatos> /type/int/nat -> |is(gt(0))
[ERROR] [/sys/scheduler] [/mmadt/int] 0 accessed as uri
thrown at inst /type/int/nat => gt(0) [0=>'/type/int/nat -> |is(gt(0))',code=>'/type/int/nat -> |is(gt(0))']
fhatos> nat[6]
==>nat[6]
fhatos> nat[-6]
[ERROR] [/sys/scheduler] [/sys/lang/parser] -6 is not a /mmadt/ext/nat as defined by nat()[is(gte(0))]
fhatos> nat[3].plus(2)
==>nat[5]
fhatos> nat[3].mult(-2)
[ERROR] [/sys/scheduler] [/sys/lang/parser] -6 is not a /mmadt/ext/nat as defined by nat()[is(gte(0))]
thrown at inst nat[3] => mult(0?int=>-2)[cpp] [0=>'nat[3].mult(-2)',0?int=>-2]
Process Types
A simple mm-ADT program is defined below.
The program is a specialization of the poly-type rec
called thread
, where thread
is abstractly defined as
The thread
object is published to the fURI endpoint esp32@127.0.0.1/scheduler/threads/logger
.
The scheduler spawns the program on an individual thread
accessible via the target fURI.
Once spawned, the setup
function prints the thread’s id and halts.
The Router Structure
Every fhatOS machine has a single router.
The function of the router is to:
-
Route read/write requests to respective structures.
-
Coordinate with remote routers on remote read/write requests.
-
Manage pattern conflicts between structures.
-
Manage fURI query extensions (
?
modulators).
fhatos> /io/console/config/nest->3
==>3
fhatos> */sys/router/#/
>[
==>/sys/router=>[
===>frame=>[rec][_]
===>config=>[
====>resolve=>[namespace=>[:=>/mmadt/,fos:=>/fos/],auto_prefix=>[,/mmadt/,/mmadt/ext/,/fos/,/fos/sys/,/fos/io/,/fos/sensor/,/fos/ui/,/fos/util/,/sys/],query=>[write=>[lock=>to_do]],default_config=>[query=>[write=>[sub=>noobj]]]]
==>]
===>query=>[
====>write=>[lock=>lock?obj{?}<=obj{?}()[cpp],sub=>sub?obj{?}<=obj()[cpp]]
==>]
===>structure=>[
====>/sys/#
====>/mnt/#
====>/boot/#
====>/fos/#
====>/mmadt/#
====>/io/#
====>+/#
====>/disk/#
====>/shared/#
====>/bus/#
==>]
=>]@/sys/router
>]
The router manages access to physical memory.
Physical memory is partitioned by structures.
The address space of a structure is the (query-less) fURI.
Structures have an associated pattern fURI defining the boundaries of their storage space.
Structures can not have overlapping address spaces.
Every structure implements the structure.hpp
and ultimately, is an obj
.
-
There are structures that encode
objs
in physical memory (e.g.heap
). -
There are structures that encode
objs
on disk (e.g.fs
— filesystem). -
There are structures that encode
objs
on a remote broker (e.g.mqtt
). -
There are structures that encode
objs
in the Bluetooth hierarchy (e.g.bt
). -
There are structures that encode
objs
on RFID chips (e.g.rfid
). -
There are structures that encode
objs
as scoped variables when evaluating code (e.g.frame
). -
There are structures that encode other structures (e.g.
mnt
).
The aggregate of all structures accessible through the router defines the complete memory footprint of a FHatOs instance.
fhatos> a -> 'snowbutt' (1)
==>'snowbutt'
fhatos> *a (2)
==>'snowbutt'
fhatos> a?sub -> |to(b) (3)
==>a?sub
fhatos> *a?sub (4)
>sub[
==>source=>/io/console
==>pattern=>a
==>on_recv=>a?sub
>]
fhatos> a -> 'meangirl' (5)
==>'meangirl'
fhatos> *b (6)
==>a?sub
1 | A request to write str['snowbutt'] to uri[a] is sent to the router. |
2 | A request to read the obj at uri[a] is sent to the router. |
3 | A subscription request to receive notifications about uri[a] is sent to the router. |
4 | A request to read the subscriptions of uri[a] is sent to the router. |
5 | A request to write str['meangirl'] to uri[a] is sent to the router. |
6 | A request to read uri[b] is sent to the router. |
The above example makes salient the router’s role is structure usage.
Not only are read/write requests managed by the router, but also subscriptions and the evaluation of their associated on_recv
-code.
However, ultimately, the router serves as a simple singleton proxy to the structures it manages.
It’s in the structures where the heavily lifting of the memory operations takes place.
Structure Reading and Writing
Every structure supports 2 primary operations:
\$\text{read} : U \rightarrow O\$ The router is given a fURI |
|
\$\text{write}: (U \times O) \rightarrow \emptyset\$ |
|
A read accepts a direct fURI (called an id
) or a match fURI (called a pattern
).
Within the category of id
and pattern
, there are node
fURIs and branch
fURIs.
An example itemization is provided below:
-
id
: an unambiguous fURI that references a single addressable location in the structure.-
node
: the address of a specificobj
. -
branch
: the root address of a collection ofobjs
.
-
-
pattern
: a fURI containing one or more wildcard path segments (+
or#
).-
node
: a pattern referencing zero or moreobjs
. -
branch
: a pattern referencing zero or more collections ofobjs
.
-
|
The first line in the example appears to be 4 individual statements.
In fact, it is a single fluent expression. The signature of the
|
Query Processors
Every fURI can have any number of key/value(s) pairs attached to it via the
?
query encoding scheme defined by the W3C URI specification.
Modules can be added to structures enabling different behaviors on read/write given associated, relevant ?
parameters.
Example modules that come preloaded with fhATos are:
-
pubsub
: supports asynchronous, event-based access to structureobjs
.-
a?sub → _
(subscribe ) -
a?sub → noobj
(unsubscribe) -
sub[source⇒uri, pattern⇒uri, on_recv⇒obj]
-
msg[target⇒uri, payload⇒obj, retain⇒bool]
-
-
lock
: provides resource locking semantics to reading and writingobjs
in a concurrent environment.-
a?lock=w
(prevent writes to theobj
ata
) -
a?lock=rw
(prevent reads and writes to theobj
ata
) -
a?lock=false
(unlock theobj
ata
)
-
-
type
: provides anobj
type system encoded within anobj’s
type fURI.-
nat?dom=int&dc=1,1&rng=int&rc=1,1
(theinst
signature ofnat?int⇐int()[…]
)
-
Other modules can be created and deployed across a FHaToS cluster.
1.plus(2)
Embedding
mm-ADT was designed to support the creation and manipulation of abstract data types — the "ADT" in mm-ADT. When expressing abstract data types is natural, then it’s possible to leverage multiple models such as key/value, document, relational, vector, graph, and the various nooks and crannies between — the "mm" in mm-ADT.
mm-ADT’s URI addressing scheme makes it possible to embed an array data types into the underlying FhATOs structure. This section will explore the following considerations when designing a multi-model abstract data type.
-
spatial encodings
-
schema encodings
-
language encodings
Spatial Consideration when Embedding
fhatos> 1.plus(2)
==>3
A matrix is an \$n \times m\$ data structure composed of \$n\$ vectors/row, each with \$m\$ elements/columns.
A relational database table is an example of a matrix, where the entries typically span numeric and non-numeric data types.
Three general approaches to embedding a matrix or table into a fos:structure
are presented below, where each makes different space/time tradeoffs.
|
|
|
|
|
|
1 | Retrieve the first element of matrix m . |
2 | Retrieve the first row of matrix m . |
3 | Retrieve the first column of matrix m . |
The above example demonstrates the power of structural embeddings.
The platonic matrix m
was embedded in a structure using 3 different representations: entry-wise, row-wise, and row-column wise.
Next, each embedding was read: an element read, a row read and a column read.
The expression used to read from each of the three embeddings is the same and so is the result.
This is possible because a structure resolves up the fURI path hierarchy until it finds a match.
Once found, it then traverses within the match to resolve the remaining path segments.
embedding | single-element | row-access | column-access |
---|---|---|---|
entry |
\$O(1)\$ |
\$O(n)\$ |
\$O(m)\$ |
row |
\$O(m)\$ |
\$O(1)\$ |
\$O(m)\$ |
row_column |
\$O(1)\$ |
\$O(1)\$ |
\$O(1)\$ |
The different embeddings also have different space costs, where space is defined as the amount of data accessed (i.e. retrieved from the structure) in order to satisfy the resolution of the respective fURI.
embedding | single-element | row-access | column-access |
---|---|---|---|
entry |
\$O(1)\$ |
\$O(n)\$ |
\$O(m)\$ |
row |
\$O(n)\$ |
\$O(n)\$ |
\$O(n+m)\$ |
row_column |
\$O(n+m)\$ |
\$O(n+m)\$ |
\$O(n+m)\$ |
[a=>[b,c]]
[■]
[■] / \
[b=>c][■] [■][d=>e]
[a=>[b=>c,d=>e]]] [a=>[b=>c,d=>e]]]
^ ^
| |
x x/
The Scheduler Process
A FhatOS Console
fURI and MQTT
MQTT is a publish/subscribe message passing protocol that has found extensive usage in embedded systems. Hierarchically specified topics can be subscribed and published to. In MQTT, there is no direct communication between actors, though such behavior can be simulated if an actor’s mailbox is a unique topic. fhAToS leverages MQTT, but from the vantage point of URIs instead of topics with message routing being location-aware. There exist three MQTT routers:
-
MonadRouter
: An MQTT router scoped to an active monad (thread) processing a monoid (program). -
MonoidRouter
: An MQTT router scoped to a monoid (program). -
HostRouter
: An MQTT router scoped to the current host (machine). -
ClusterRouter
: An MQTT router scoped to the current intranet (cluster). -
GlobalRouter
: An MQTT router scoped to the Internet. -
MetaRouter
: An MQTT router dynamically scoped to other routers based on fURI endpoints.
fhatos> {1,2,3}
==>1
==>2
==>3
fhatos> {1,2,3}.plus(10)
==>11
==>12
==>13
fhatos> {1,2,3}.plus(_)
==>2
==>4
==>6
fhatos> {1,2,3}.plus(plus(_))
==>3
==>6
==>9
FhatOS Modules
Kernel Modules
mmADT Module (mmadt)
Type Module (mmadt:type)
Parser Module (mmadt:parser)
Scheduler Module (scheduler)
Router Module (router)
Core Modules
Pin Modules
GPIO (gpio)
Hardware devices with digital general purpose input/output (GPIO) can be manipulated with /fos/io/gpio
.
PWM (pwm)
Pins that support pulse-wave modulation can be manipulated with /fos/io/pwm
.
i2c (i2c)
Two wire access
FileSystem Module (fs)
Terminal Module (terminal)
REPL Module (repl)
Logging Module (log)
Embedded Systems Modules
Sensors
Actuators
Reference
mm-ADT Core Instructions
as [_]
block |
is
plus
fhatos> true.plus(false)
==>true
fhatos> 1.plus(2)
==>3
fhatos> 'a'.plus('b')
==>'ab'
mult
mod
lift ^
drop v
split -<
each =
within _/ \_
merge >-
from *
to ->
get @
pass -->
match ~
fhatos> [a=>2].match([a=>3])
[ERROR] [/mmadt/rec] match inst unresolved
lhs id inst id resolve obj
->[/mmadt/rec] match => noobj
-->[/mmadt/rec] match => noobj
--->[ ] match => noobj
fhatos> [a=>2].match([a=>_])
[ERROR] [/mmadt/rec] match inst unresolved
lhs id inst id resolve obj
->[/mmadt/rec] match => noobj
-->[/mmadt/rec] match => noobj
--->[ ] match => noobj
eq
neq
gt
lt
gte
lte
FhatOS Types
Process Types
thread
fiber
coroutine
PubSub Types
sub
sub[[:source=>_, :pattern=>_, :on_recv=>bcode[_]]]
msg
msg[[:target=>uri[_], :payload=>_, :retain=>bool[_]]]