Web app on AWS Lambda with Janet
28 Oct 2021Janet is a small functional and imperative programming language. The language has some similarities to Clojure with its Lispy syntax, its data notation, and with its proper REPL for dynamic developing within a running system. Janet also compiles its runtime and VM to C and can therefore run almost anywhere.
All of Janet's features make it a good candidate to make AWS Lambda serverless functions with. This is because Janet programs start up and compile quickly, certainly when compared to a combination of Clojure and native-image compilation. Postings is a simple forum web app on AWS Lambda behind an API Gateway and using DynamoDB as the database.
Postings web app at https://iakerss4c6.execute-api.eu-central-1.amazonaws.com, source at https://github.com/thegeez/janet-postingsDifferences between Janet & Clojure
Janet and Clojure look alike and share many concepts and programming approaches. This is usefull to get started quickly, but can also trip you up when things that look the same in both languages behave slightly different. Here are some of the things I ran into:
- Immutable and mutable data - Janet has immutable data for values, but for data that will change over time you'll need to use the mutable variants.
{:struct "immutable data"} # an immutable struct # Can't create another immutable datastructure through 'assoc' or 'conj' from a struct, need to use a table: (def table @{:mutable "data"}) (put table :extra-key "value") table #=> @{:mutable "data :extra-key "value"}
- Data and value equality - Immutable data has value equality semantics, the mutable data structures do not.
(= {:a 1} {:a 1}) #=> true, immutable structs are equal (= @{:a 1} @{:a 1}) #=> false (!!), mutable tables are not equal (deep= @{:a 1} @{:a 1}) #=> true, can check mutable data structures for equality with deep=
- nil punning - If a construct in Janet expects a data structure you can't pass nil to it instead of an empty collection. (This is the thing from Clojure that trips me up most often in Janet.)
(loop [number :in [1 2 3]] ...do something with number...) (loop [number :in nil] ...collection is empty...) #=> error: expected iterable type, got nil
- Dynamic development with the REPL - Janet uses early binding. So if you recompile a function in the REPL, any function that uses the recompiled function will not use the newest definition. This is usually not what you want. To get the late binding that Clojure uses, redefine "defn" to "varfn" during development. For other dynamic behaviors the environment in Janet is first-class and inspectable and changeable through "(curenv)".
The source for the Posting app is here: https://github.com/thegeez/janet-postings