learnxinyminutes-docs/lfe.md
2024-12-09 04:34:00 -07:00

446 lines
12 KiB
Markdown

---
name: "Lisp Flavoured Erlang (LFE)"
filename: lispflavourederlang.lfe
contributors:
- ["Pratik Karki", "https://github.com/prertik"]
---
Lisp Flavoured Erlang (LFE) is a functional, concurrent, general-purpose programming
language and Lisp dialect (Lisp-2) built on top of Core Erlang and the Erlang Virtual Machine (BEAM).
LFE can be obtained from [LFE](https://github.com/rvirding/lfe).
The classic starting point is the [LFE docs](http://docs.lfe.io).
```lisp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 0. Syntax
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; General form.
;; Lisp is comprised of two syntaxes, the ATOM and the S-expression.
;; `forms` are known as grouped S-expressions.
8 ; an atom; it evaluates to itself
:ERLANG ;Atom; evaluates to the symbol :ERLANG.
t ; another atom which denotes true.
(* 2 21) ; an S- expression
'(8 :foo t) ;another one
;;; Comments
;; Single line comments start with a semicolon; use two for normal
;; comments, three for section comments, and four fo file-level
;; comments.
;; Block Comment
#| comment text |#
;;; Environment
;; LFE is the de-facto standard.
;; Libraries can be used directly from the Erlang ecosystem. Rebar3 is the build tool.
;; LFE is usually developed with a text editor(preferably Emacs) and a REPL
;; (Read Evaluate Print Loop) running at the same time. The REPL
;; allows for interactive exploration of the program as it is "live"
;; in the system.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 1. Literals and Special Syntactic Rules
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Integers
1234 -123 ; Regular decimal notation
#b0 #b10101 ; Binary notation
#0 #10101 ; Binary notation (alternative form)
#o377 #o-111 ; Octal notation
#d123456789 #d+123 ; Explicitly decimal notation
#xc0ffe 0x-01 ; Hexadecimal notation
#2r1010 #8r377 ;Notation with explicit base (up to 36)
#\a #$ #\ä #\🐭 ;Character notation (the value is the Unicode code point of the character)
#\x1f42d; ;Character notation with the value in hexadecimal
;;; Floating point numbers
1.0 +2.0 -1.5 1.0e10 1.111e-10
;;; Strings
"any text between double quotes where \" and other special characters like \n can be escaped".
; List String
"Cat: \x1f639;" ; writing unicode in string for regular font ending with semicolon.
#"This is a binary string \n with some \"escaped\" and quoted (\x1f639;) characters"
; Binary strings are just strings but function different in the VM.
; Other ways of writing it are: #B("a"), #"a", and #B(97).
;;; Character escaping
\b ; => Backspace
\t ; => Tab
\n ; => Newline
\v ; => Vertical tab
\f ; => Form Feed
\r ; => Carriage Return
\e ; => Escape
\s ; => Space
\d ; => Delete
;;; Binaries
;; It is used to create binaries with any contents.
#B((#"a" binary) (#"b" binary)) ; #"ab" (Evaluated form)
;;; Lists are: () or (foo bar baz)
;;; Tuples are written in: #(value1 value2 ...). Empty tuple #() is also valid.
;;; Maps are written as: #M(key1 value1 key2 value2 ...). Empty map #M() is also valid.
;;; Symbols: Things that cannot be parsed. Eg: foo, Foo, foo-bar, :foo
| foo | ; explicit construction of symbol by wrapping vertical bars.
;;; Evaluation
;; #.(... some expression ...). E.g. '#.(+ 1 1) will evaluate the (+ 1 1) while it ;; reads the expression and then be effectively '2.
;; List comprehension in LFE REPL
lfe> (list-comp
((<- x '(0 1 2 3)))
(trunc (math:pow 3 x)))
(1 3 9 27)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 2. Core forms
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; These forms are the same as those found in Common Lisp and Scheme.
(quote e)
(cons head tail)
(car e)
(cdr e)
(list e ... )
(tuple e ... )
(binary seg ... )
(map key val ...), (map-get m k), (map-set m k v ...), (map-update m k v ...)
(lambda (arg ...) ...)
(match-lambda
((arg ... ) {{(when e ...)}} ...) ; Matches clauses
... )
(let ((pat {{(when e ...)}} e)
...)
... )
(let-function ((name lambda|match-lambda) ; Only define local
... ) ; functions
... )
(letrec-function ((name lambda|match-lambda) ; Only define local
... ) ; functions
... )
(let-macro ((name lambda-match-lambda) ; Only define local
...) ; macros
...)
(progn ... )
(if test true-expr {{false-expr}})
(case e
(pat {{(when e ...)}} ...)
... ))
(receive
(pat {{(when e ...)}} ... )
...
(after timeout ... ))
(catch ... )
(try
e
{{(case ((pat {{(when e ...)}} ... )
... ))}}
{{(catch
; Next must be tuple of length 3!
(((tuple type value ignore) {{(when e ...)}}
... )
... )}}
{{(after ... )}})
(funcall func arg ... )
(call mod func arg ... ) - Call to Erlang Mod:Func(Arg, ... )
(define-module name declaration ... )
(extend-module declaration ... ) - Define/extend module and declarations.
(define-function name lambda|match-lambda)
(define-macro name lambda|match-lambda) - Define functions/macros at top-level.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 3. Macros
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Macros are part of the language and allow you to create abstractions
;; on top of the core language and standard library that move you closer
;; toward being able to directly express the things you want to express.
;; Top-level function
(defun name (arg ...) ...)
;; Adding comments in functions
(defun name
"Toplevel function with pattern-matching arguments"
((argpat ...) ...)
...)
;; Top-level macro
(defmacro name (arg ...) ...)
(defmacro name arg ...)
;; Top-level macro with pattern matching arguments
(defmacro name
((argpat ...) ...)
...)
;; Top-level macro using Scheme inspired syntax-rules format
(defsyntax name
(pat exp)
...)
;;; Local macros in macro or syntax-rule format
(macrolet ((name (arg ... ) ... )
... )
... )
(syntaxlet ((name (pat exp) ...)
...)
...)
;; Like CLISP
(prog1 ...)
(prog2 ...)
;; Erlang LFE module
(defmodule name ...)
;; Erlang LFE record
(defrecord name ...)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 4. Patterns and Guards
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Using patterns in LFE compared to that of Erlang
;; Erlang ;; LFE
;; {ok, X} (tuple 'ok x)
;; error 'error
;; {yes, [X|Xs]} (tuple 'yes (cons x xs))
;; <<34,F/float>> (binary 34 (f float))
;; [P|Ps]=All (= (cons p ps) all)
_ ; => is don't care while pattern matching
(= pattern1 pattern2) ; => easier, better version of pattern matching
;; Guards
;; Whenever pattern occurs (let, case, receive, lc, etc) it can be followed by an optional
;; guard which has the form (when test ...).
(progn gtest ...) ;; => Sequence of guard tests
(if gexpr gexpr gexpr)
(type-test e)
(guard-bif ...) ;; => Guard BIFs, arithmetic, boolean and comparison operators
;;; REPL
lfe>(set (tuple len status msg) #(8 ok "Trillian"))
#(8 ok "Trillian")
lfe>msg
"Trillian"
;;; Program illustrating use of Guards
(defun right-number?
((x) (when (orelse (== x 42) (== x 276709)))
'true)
((_) 'false))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 5. Functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; A simple function using if.
(defun max (x y)
"The max function."
(if (>= x y) x y))
;; Same function using more clause
(defun max
"The max function."
((x y) (when (>= x y)) x)
((x y) y))
;; Same function using similar style but using local functions defined by flet or fletrec
(defun foo (x y)
"The max function."
(flet ((m (a b) "Local comment."
(if (>= a b) a b)))
(m x y)))
;; LFE being Lisp-2 has separate namespaces for variables and functions
;; Both variables and function/macros are lexically scoped.
;; Variables are bound by lambda, match-lambda and let.
;; Functions are bound by top-level defun, flet and fletrec.
;; Macros are bound by top-level defmacro/defsyntax and by macrolet/syntaxlet.
;; (funcall func arg ...) like CL to call lambdas/match-lambdas
;; (funs) bound to variables are used.
;; separate bindings and special for apply.
apply _F (...),
apply _F/3 ( a1, a2, a3 )
;; Cons'ing in function heads
(defun sum (l) (sum l 0))
(defun sum
(('() total) total)
(((cons h t) total) (sum t (+ h total))))
;; ``cons`` literal instead of constructor form
(defun sum (l) (sum l 0))
(defun sum
(('() total) total)
((`(,h . ,t) total) (sum t (+ h total))))
;; Matching records in function heads
(defun handle_info
(('ping (= (match-state remote-pid 'undefined) state))
(gen_server:cast (self) 'ping)
`#(noreply ,state))
(('ping state)
`#(noreply ,state)))
;; Receiving Messages
(defun universal-server ()
(receive
((tuple 'become func)
(funcall func))))
;; another way for receiving messages
(defun universal-server ()
(receive
(`#(become ,func)
(funcall func))))
;; Composing a complete function for specific tasks
(defun compose (f g)
(lambda (x)
(funcall f
(funcall g x))))
(defun check ()
(let* ((sin-asin (compose #'sin/1 #'asin/1))
(expected (sin (asin 0.5)))
(compose-result (funcall sin-asin 0.5)))
(io:format "Expected answer: ~p~n" (list expected))
(io:format "Answer with compose: ~p~n" (list compose-result))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 6. Concurrency
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Message passing as done by Erlang's light-weight "processes".
(defmodule messenger-back
(export (print-result 0) (send-message 2)))
(defun print-result ()
(receive
((tuple pid msg)
(io:format "Received message: '~s'~n" (list msg))
(io:format "Sending message to process ~p ...~n" (list pid))
(! pid (tuple msg))
(print-result))))
(defun send-message (calling-pid msg)
(let ((spawned-pid (spawn 'messenger-back 'print-result ())))
(! spawned-pid (tuple calling-pid msg))))
;; Multiple simultaneous HTTP Requests:
(defun parse-args (flag)
"Given one or more command-line arguments, extract the passed values.
For example, if the following was passed via the command line:
$ erl -my-flag my-value-1 -my-flag my-value-2
One could then extract it in an LFE program by calling this function:
(let ((args (parse-args 'my-flag)))
...
)
In this example, the value assigned to the arg variable would be a list
containing the values my-value-1 and my-value-2."
(let ((`#(ok ,data) (init:get_argument flag)))
(lists:merge data)))
(defun get-pages ()
"With no argument, assume 'url parameter was passed via command line."
(let ((urls (parse-args 'url)))
(get-pages urls)))
(defun get-pages (urls)
"Start inets and make (potentially many) HTTP requests."
(inets:start)
(plists:map
(lambda (x)
(get-page x)) urls))
(defun get-page (url)
"Make a single HTTP request."
(let* ((method 'get)
(headers '())
(request-data `#(,url ,headers))
(http-options ())
(request-options '(#(sync false))))
(httpc:request method request-data http-options request-options)
(receive
(`#(http #(,request-id #(error ,reason)))
(io:format "Error: ~p~n" `(,reason)))
(`#(http #(,request-id ,result))
(io:format "Result: ~p~n" `(,result))))))
```
## Further Reading
* [LFE DOCS](http://docs.lfe.io)
* [LFE GitBook](https://lfe.gitbooks.io/reference-guide/index.html)
* [LFE Wiki](https://en.wikipedia.org/wiki/LFE_(programming_language))
## Extra Info
* [LFE PDF](http://www.erlang-factory.com/upload/presentations/61/Robertvirding-LispFlavouredErlang.pdf)
* [LFE mail](https://groups.google.com/d/msg/lisp-flavoured-erlang/XA5HeLbQQDk/TUHabZCHXB0J)