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

12 KiB

name filename contributors
Lisp Flavoured Erlang (LFE) lispflavourederlang.lfe
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. The classic starting point is the LFE docs.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 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

Extra Info