2017-08-15 18:00:21 +00:00
|
|
|
---
|
2024-12-09 11:34:00 +00:00
|
|
|
name: "Lisp Flavoured Erlang (LFE)"
|
2017-08-16 04:18:16 +00:00
|
|
|
filename: lispflavourederlang.lfe
|
2017-08-15 18:00:21 +00:00
|
|
|
contributors:
|
|
|
|
- ["Pratik Karki", "https://github.com/prertik"]
|
|
|
|
---
|
|
|
|
|
2024-05-31 14:55:46 +00:00
|
|
|
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).
|
2017-08-15 18:00:21 +00:00
|
|
|
|
2024-05-31 14:55:46 +00:00
|
|
|
LFE can be obtained from [LFE](https://github.com/rvirding/lfe).
|
|
|
|
The classic starting point is the [LFE docs](http://docs.lfe.io).
|
2017-08-15 18:00:21 +00:00
|
|
|
|
2017-08-18 17:12:58 +00:00
|
|
|
```lisp
|
2017-08-15 18:00:21 +00:00
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;;; 0. Syntax
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
|
|
;;; General form.
|
|
|
|
|
2024-05-31 14:55:46 +00:00
|
|
|
;; Lisp is comprised of two syntaxes, the ATOM and the S-expression.
|
2017-08-15 18:00:21 +00:00
|
|
|
;; `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
|
2024-05-31 14:55:46 +00:00
|
|
|
;; (Read Evaluate Print Loop) running at the same time. The REPL
|
2017-08-15 18:00:21 +00:00
|
|
|
;; allows for interactive exploration of the program as it is "live"
|
|
|
|
;; in the system.
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;;; 1. Literals and Special Syntactic Rules
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
|
|
;;; Integers
|
|
|
|
|
2024-05-31 14:55:46 +00:00
|
|
|
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)
|
2017-08-15 18:00:21 +00:00
|
|
|
#\a #$ #\ä #\🐭 ;Character notation (the value is the Unicode code point of the character)
|
2024-05-31 14:55:46 +00:00
|
|
|
#\x1f42d; ;Character notation with the value in hexadecimal
|
2017-08-15 18:00:21 +00:00
|
|
|
|
|
|
|
;;; Floating point numbers
|
2024-05-31 14:55:46 +00:00
|
|
|
1.0 +2.0 -1.5 1.0e10 1.111e-10
|
2017-08-15 18:00:21 +00:00
|
|
|
|
|
|
|
;;; 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"
|
2024-05-31 14:55:46 +00:00
|
|
|
; Binary strings are just strings but function different in the VM.
|
2017-08-15 18:00:21 +00:00
|
|
|
; Other ways of writing it are: #B("a"), #"a", and #B(97).
|
|
|
|
|
|
|
|
|
|
|
|
;;; Character escaping
|
|
|
|
|
2024-05-31 14:55:46 +00:00
|
|
|
\b ; => Backspace
|
|
|
|
\t ; => Tab
|
|
|
|
\n ; => Newline
|
|
|
|
\v ; => Vertical tab
|
|
|
|
\f ; => Form Feed
|
|
|
|
\r ; => Carriage Return
|
|
|
|
\e ; => Escape
|
|
|
|
\s ; => Space
|
|
|
|
\d ; => Delete
|
2017-08-15 18:00:21 +00:00
|
|
|
|
|
|
|
;;; Binaries
|
|
|
|
;; It is used to create binaries with any contents.
|
2024-05-31 14:55:46 +00:00
|
|
|
#B((#"a" binary) (#"b" binary)) ; #"ab" (Evaluated form)
|
2017-08-15 18:00:21 +00:00
|
|
|
|
|
|
|
;;; 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.
|
|
|
|
|
2024-05-31 14:55:46 +00:00
|
|
|
;;; Evaluation
|
2017-08-15 18:00:21 +00:00
|
|
|
|
|
|
|
;; #.(... 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
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
2024-05-31 14:55:46 +00:00
|
|
|
;; These forms are the same as those found in Common Lisp and Scheme.
|
2017-08-15 18:00:21 +00:00
|
|
|
|
|
|
|
(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
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
2024-05-31 14:55:46 +00:00
|
|
|
;; 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
|
2017-08-15 18:00:21 +00:00
|
|
|
;; 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 ...) ...)
|
|
|
|
...)
|
|
|
|
|
2024-05-31 14:55:46 +00:00
|
|
|
;; Top-level macro using Scheme inspired syntax-rules format
|
2017-08-15 18:00:21 +00:00
|
|
|
|
|
|
|
(defsyntax name
|
|
|
|
(pat exp)
|
|
|
|
...)
|
|
|
|
|
|
|
|
;;; Local macros in macro or syntax-rule format
|
|
|
|
|
|
|
|
(macrolet ((name (arg ... ) ... )
|
|
|
|
... )
|
|
|
|
... )
|
2024-05-31 14:55:46 +00:00
|
|
|
|
2017-08-15 18:00:21 +00:00
|
|
|
(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
|
2024-05-31 14:55:46 +00:00
|
|
|
|
2017-08-15 18:00:21 +00:00
|
|
|
(= pattern1 pattern2) ; => easier, better version of pattern matching
|
2024-05-31 14:55:46 +00:00
|
|
|
|
2017-08-15 18:00:21 +00:00
|
|
|
;; Guards
|
|
|
|
|
2024-05-31 14:55:46 +00:00
|
|
|
;; Whenever pattern occurs (let, case, receive, lc, etc) it can be followed by an optional
|
2017-08-15 18:00:21 +00:00
|
|
|
;; 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.
|
|
|
|
|
2024-05-31 14:55:46 +00:00
|
|
|
;; (funcall func arg ...) like CL to call lambdas/match-lambdas
|
2017-08-15 18:00:21 +00:00
|
|
|
;; (funs) bound to variables are used.
|
|
|
|
|
|
|
|
;; separate bindings and special for apply.
|
2024-05-31 14:55:46 +00:00
|
|
|
apply _F (...),
|
2017-08-15 18:00:21 +00:00
|
|
|
apply _F/3 ( a1, a2, a3 )
|
2024-05-31 14:55:46 +00:00
|
|
|
|
2017-08-15 18:00:21 +00:00
|
|
|
;; Cons'ing in function heads
|
|
|
|
(defun sum (l) (sum l 0))
|
|
|
|
(defun sum
|
|
|
|
(('() total) total)
|
|
|
|
(((cons h t) total) (sum t (+ h total))))
|
2024-05-31 14:55:46 +00:00
|
|
|
|
2017-08-15 18:00:21 +00:00
|
|
|
;; ``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))))
|
2024-05-31 14:55:46 +00:00
|
|
|
|
2017-08-15 18:00:21 +00:00
|
|
|
;; 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))))
|
2024-05-31 14:55:46 +00:00
|
|
|
|
2017-08-15 18:00:21 +00:00
|
|
|
;; 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
|
|
|
|
|
2024-05-31 14:55:46 +00:00
|
|
|
* [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))
|
2017-08-15 18:00:21 +00:00
|
|
|
|
|
|
|
## Extra Info
|
|
|
|
|
2024-05-31 14:55:46 +00:00
|
|
|
* [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)
|