Git Product home page Git Product logo

esugar's Introduction

An Erlang Syntactic Sugar Library

Motivation

I probably met Erlang in 2014 and fell in love with this "micro operating system" that solved many of my work problems. But in communicating with others, I also heard some complaints about Erlang, the most of which is about its grammar! Until one day I found an open source log library named Lager, there is a trick to use the compiler parameter parse_transform to convert the source code to your own wishes. So I went to check out the relevant information of parse_transform and wrote this project: on the one hand, it shows as demo that we can do a lot of cool things with parse_transform; on the other hand, you can also think of it as an Erlang syntactic sugar library is used or extended.

Pipe Operator

Many languages (such as F# and Elixir) support the pipeline operator, which passes the result of the previous expression as a parameter to the next function (call), forming a call chain. For example, if we want to implement an md5 function with Erlang, the traditional way of writing might be like this:

-spec md5(Data :: iodata()) -> Digest :: string().
md5(Data) ->
    _Digest = string:to_lower(lists:flatten(lists:map(fun(E) ->
        [integer_to_list(X, 16) || X <- [E div 16, E rem 16]]
    end, binary_to_list(erlang:md5(Data))))).

If you are tired of the nested call above, then we will allow the following pipelining, which is equivalent to the previous traditional notation:

-spec md5(Data :: iodata()) -> Digest :: string().
md5(Data) ->
    _Digest = pipe@(Data
        ! fun erlang:md5/1
        ! binary_to_list()
        ! lists:map(fun(E) -> [integer_to_list(X, 16)
                   || X <- [E div 16, E rem 16]] end)
        ! lists:flatten()
        ! fun string:to_lower/1).

Do Block

The most annoying thing about writing business logic with Erlang is the rocket-like nesting. For example, let's write a function that returns the result of multiplying and dividing two integers.

muldiv(First, Second) ->
    case is_integer(First) of
        true ->
            case is_integer(Second) of
                true ->
                    Product = First * Second,
                    case Second =/= 0 of
                        true ->
                            Quotient = First div Second,
                            {ok, {Product, Quotient}};
                        false ->
                            {error, "Second must not be zero!"}
                    end;
                false ->
                    {error, "Second must be an integer!"}
            end;
        false ->
            {error, "First must be an integer!"}
    end.

Does it look like a rocket that sprints to the right? This is not an exaggeration. There may be more judgment branches in the real business logic, and it is even more distressing when a change in requirements requires a new judgment. One way to solve this problem is to split business logic reasonably, but this is not a panacea that can be used at any time. Here we provide a do syntax like Haskell that allows you to avoid this annoying nesting.

muldiv(First, Second) ->
    do@([esugar_do_transform_error ||
        case is_integer(First) of
        true -> return(next);
        false -> fail("First must be an integer!")
        end,
        case is_integer(Second) of
        true -> return(next);
        false -> fail("Second must be an integer!")
        end,
        Product = First * Second,
        case Second =/= 0 of
        true -> return(next);
        false -> fail("Second must not be zero!")
        end,
        Quotient = First div Second,
        return({Product, Quotient})
    ]).

To use the do syntax like above, you only need to implement your own monad instance (here we implement esugar_do_transform_error).

Import As

We know that many languages have the concept of modules, and a module contains a large number of functions. When other files want to use these functions, they need to be imported into the file. Erlang does not import but uses the M:F(...) method to call the external function. Of course, it also supports the syntax of -import(M, [F/A]). Specify the module name and you can call F(...) directly. We now provide a more powerful import syntax that renames the imported function so that you can call the external function with the name you like in the current module.

-module(example).
-compile([{parse_transform, esugar_import_as_transform}]).
-import_as({lists, [{seq/2, range}, {reverse/1, rev}]}).
-export([run/0]).

run() ->
    [3, 2, 1] = rev(range(1, 3)).

However, I prefer the most primitive method of calling, even the import syntax is rarely used. This avoids many low-level errors, so the import_as syntax we implemented is not recommended.

Bind Repeatedly

In Erlang, the variable is actually immutable. When we bind X to the value 1 with X = 1, we can't say no later and let X = 2. The benefits of this immutable property are bound to be written in any book that introduces functional programming, and I won't say much here. So how do we modify a variable, Erlang says: "You can't modify it at any time, but you can create a new variable, why do you want to modify it?" So Erlang encourages us to write code as follow:

norm(X, Y) ->
    T1 = 0,
    T2 = T1 + X * X,
    T3 = T2 + Y * Y,
    T4 = math:sqrt(T3),
    T4.

I am just exaggerating the example for example, you certainly don't have to create so many new variables. However, after using the repeated binding syntax, we can actually write this:

norm(X, Y) ->
    T = 0,
    T = T + X * X,
    T = T + Y * Y,
    T = math:sqrt(T),
    T.

Doesn't it mean that Erlang doesn't let us modify the variables that are already bound, and it does. The above code will actually be converted to the following transition form at compile time:

norm(X@1, Y@1) ->
    T@1 = 0,
    T@2 = T@1 + X@1 * X@1,
    T@3 = T@2 + Y@1 * Y@1,
    T@4 = math:sqrt(T@3),
    T@4.

That is to say, in each new matching mode as an left value, even if the same variable name, the number is converted into a new variable. As the variable name of the right value, the largest number that already exists is used. Then there is one problem left. If I want to use the variable that I just bound in the pattern instead of re-creating it, you can add @ to the variable at this time.

norm(X, Y) ->
    T = 0,
    T = T + X * X,
    T = T + Y * Y,
    T@ = X * X + Y * Y,
    T = math:sqrt(T),
    T.

It is equivalent to the following:

norm(X@1, Y@1) ->
    T@1 = 0,
    T@2 = T@1 + X@1 * X@1,
    T@3 = T@2 + Y@1 * Y@1,
    T@3 = X@1 * X@1 + Y@1 * Y@1,
    T@4 = math:sqrt(T@3),
    T@4.

At The End

Finally, maybe you will ask, what is the use of these things? I will tell you that it doesn't make much sense. Nothing is impossible because of the lack of them, as I said at the beginning: You can think of it as a demo, showing that we can do a lot of interesting things in the grammatical level of Erlang; you can also think of it as a library to use or extend, but I can't guarantee its stability. _(:з」∠)_

esugar's People

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.