Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

How Elixir helps Erlang for better Engineering

Ivan Rublev
February 25, 2020

How Elixir helps Erlang for better Engineering

Ivan Rublev

February 25, 2020
Tweet

More Decks by Ivan Rublev

Other Decks in Programming

Transcript

  1. LevviBraun IvanRublev ivanrublev.me How Elixir helps Erlang for better Engineering

    Some differences between languages running on the same platform 2020
  2. OTP / BEAM VM • Ericsson - 1990 / 1998

    / 2006 • Actor Model • Lightweight isolated processes • Asynchronous messages • Erlang • Process links / Supervisors • Distributed nodes • Schedulers / Preemptive multitasking • Swiss army knife platform for building messaging systems Elixir ?
  3. Syntax send_message(Id, ProgressDate, Pause) -> R = telegram:send_message(Id, ProgressDate), lager:debug("Send

    message to chat_id: ~p, pause: ~p", [Id, Pause]), util:pause(Pause), R. def send_message(id, progress_date, pause) do r = Telegram.send_message(id, progress_date) Logger.debug("Send message to chat_id: #{id}, pause: #{pause}") :util.pause(pause) r end 1> Atom = atom. 2> [atom, Atom]. [atom,atom] 1> atom = :atom 2> [:atom, atom] [:atom, :atom]
  4. Data Structures #{"dog" => "winston", "fish" => "mrs.blub"}. %{"dog" =>

    "winston", "fish" => "mrs.blub"} [{size,2697}, {type,regular}]. -module(records). -compile(export_all). -record(robot, {name, type=industrial}). 1> A=#robot{}. 2> A#robot.type. industrial defmodule Robot do defstruct name: nil, type: :industrial end 1> a=%Robot{} 2> a.type :industrial [size: 2697, type: :regular]
  5. Tuples [ {year_progress_bot, [ {host, "${HOST}"}, {port, ${PORT}}, ]}, {sumo_db,

    [ {wpool_opts, [{overrun_warning, 100}]}, {log_queries, true} ]} ]. sys.config.src config.exs config :year_progress_bot, host: "${HOST}", port: ${PORT}, config :sumo_db, :wpool_opts, overrun_warning: 100 config :sumo_db, log_queries: true
  6. Strings jiffy:encode({[ {<<"method">>, <<"sendMessage">>}, {<<"text">>, <<"""/utf8>>}, {<<"chat_id">>, ChatId} ]}). Jason.encode([

    %{"method" => "sendMessage", "text" => """, "chat_id" => chat_id} ]) "hełło" [104, 101, 322, 322, 111] 'hełło' [104, 101, 322, 322, 111] List of Unicode code points / Charlist Binary string UTF-8 Latin1 (0-255) UTF-x
  7. Tests -module(endpoint_tests). -include_lib("eunit/include/eunit.hrl"). start_test_() -> {foreach, fun() -> meck:new(db, [{stub_all,

    ok}]) end, fun(_) -> meck:unload(db) end, [fun should_record_chat_id_on_start/1, ...]}. should_record_chat_id_on_start(_) -> endpoint:init({}, #{}), ?_assert(meck:called(db, add_notified_chat, [123456, …])). ... -module(endpoint_tests_SUITE). -compile(export_all). -include_lib("common_test/include/ct.hrl"). -include_lib ("etest_http/include/etest_http.hrl"). suite() -> [{timetrap,{seconds,30}}]. init_per_suite(Config) -> {ok, _} = application:ensure_all_started(cowboy). end_per_suite(_Config) -> application:stop(cowboy). init_per_testcase(_TestCase, Config) -> meck:new(calendar, [unstick, passthrough]), Config. end_per_testcase(_TestCase, _Config) -> meck:unload(calendar). all() -> [should_reply_to_chat_id_received_on_start, ...].
 should_reply_to_chat_id_received_on_start(Config) -> Res = ?perform_post(...), ?assert_json_value(<<"chat_id">>, 1213141, Res). ... Common Tests EUnit Tests defmodule TwitterWallWeb.FeedLive.Test do use TwitterWallWeb.ConnCase, async: true import Mox setup_all do on_exit(fn -> # cleanup end) :ok end describe "GET / response should" do setup %{conn: conn} do stub_with(TwitterWall.Double, 
 TwitterWall.Stub) {:ok, conn: get(conn, "/")} end test "have status 200", %{conn: conn} do assert html_response(conn, 200) end ... end end ExUnit
  8. Macros Enum.each(0..@prehandle, fn i -> def parse( <<_::binary-size(unquote(i)), ?\n, rest::binary>>,

    %{bc: bc, wc: wc, lc: lc, ns?: ns} ), do: parse(rest, acc!(unquote(i), bc, wc, lc + 1, ns)) def parse( <<_::binary-size(unquote(i)), ?\s, rest::binary>>, %{bc: bc, wc: wc, lc: lc, ns?: ns} ), do: parse(rest, acc!(unquote(i), bc, wc, lc, ns)) end) https://gist.github.com/am-kantox/afd52c2ee4c95ea25eff464e8692ac15#file-wc-ex-L57-L77 wc utility equivalent. 
 Specific functions generation, 6x times faster comparing to general recursion. -define(macro1(X, Y), {a, X, b, Y}). bar(X) -> ?macro1(a, b), ?macro1(X, 123) ---------------- bar(X) -> {a,a,b,b}, {a,X,b,123}. Function level Module level
  9. Configuration defmodule AnApp.MixProject do use Mix.Project def project do [

    app: :twitter_wall, version: "0.1." <> File.read!("BUILD.MD"), elixir: "~> 1.5”,
 ... ] end
 end Reading build version number from a file % Append build number to version number in config {ok, File} = file:open("BUILD",[read]), {ok, BuildStr} = file:read(File,1024), file:close(File), io:format("App build number is: ~s, REBAR_PROFILE is: ~s~n", [BuildStr, os:getenv("REBAR_PROFILE", "undef")]), {value, RelxTup, _} = lists:keytake(relx, 1, CONFIG), {relx, Relx} = RelxTup, {value, ReleaseTup, _} = lists:keytake(release, 1, Relx), {release, Release, Opts} = ReleaseTup, {AppName, Ver} = Release, VerUpd = Ver ++ BuildStr, ReleaseUpd = {AppName, VerUpd}, ReleaseTupUpd = {release, ReleaseUpd, Opts}, RelxUpd = lists:keystore(release, 1, Relx, ReleaseTupUpd), RelxTupUpd = {relx, RelxUpd}, CONFIG_UPD = lists:keystore(relx, 1, CONFIG, RelxTupUpd), CONFIG_UPD. rebar.config.script {erl_opts, [debug_info]}. {deps, [ {cowboy, "2.7.0"},
 ... ]}. {relx, [{release, {year_progress_bot, "0.1."}, [year_progress_bot, sasl]},
 ... }. rebar.config mix.exs
  10. Documentation http://erlang.org/doc/search https://hexdocs.pm/elixir Generated using a bunch of templates, scripts,

    and Makefiles.
 
 https://github.com/erlang/otp/tree/maint/system/doc/top Generated with ExDoc from modules comment.
 That is used on hexdocs.pm
  11. Elegant Elixir helps Erlang • Less brackets • UTF-8 Strings

    • Compact Tests • Code generation with Macros • Project configuration as a code