Erlang basics · Lesson 28 (open contents)
01 · Open erl02 · Meet common terms03 · Tell two kinds of text apart04 · Bind a variable once05 · Split and filter a list06 · Parse a number safely07 · Choose one path with case08 · Let a function keep going09 · Put code in a module10 · Compare terms and Boolean results11 · Read and update nested maps12 · Build and inspect UTF-8 binaries13 · Let clauses choose the function path14 · Solve a list twice15 · Choose with patterns, guards, and errors16 · Describe a module and one record17 · Put one tested module in Rebar318 · Return success or failure as data19 · Read and write one file safely20 · Build a data pipeline with functions21 · Keep records behind a module boundary22 · Define a module contract23 · Describe the data contract24 · Test small rules and whole flows25 · Shape builds and make a command26 · Write the project promise27 · Parse one real line28 · Keep the public API small29 · Make the promise executable30 · Package the edge and leave clues31 · Prove the project is done
ERLANG · PROJECT · LESSON 2835 minutes

Keep the public API small

Separate file access, parsing, and pure summary logic.

01 · Start with the whole example

Run this first

Run it once, change one input, and compare the new result.

Erlang
%% Export a small parser and one summary call
-module(cleaner).
-export([run/1, parse_line/1]).

run(Records) -> {ok, count(Records)}.

parse_line(Line) ->
  case binary:split(string:trim(Line), <<" ">>, [trim]) of
    [] -> skip;
    [<<"INFO">>, Message] -> {ok, {info, Message}};
    [<<"WARN">>, Message] -> {ok, {warn, Message}};
    [<<"ERROR">>, Message] -> {ok, {error, Message}};
    _ -> {error, invalid_line}
  end.

count(Records) ->
  lists:foldl(fun({Level, _}, Acc) ->
    maps:update_with(Level, fun(N) -> N + 1 end, 1, Acc)
  end, #{}, Records).
Check the result first
  • cleaner:run([{info,<<"ready">>},{info,<<"done">>}]) returns {ok,#{info => 2}}.
02 · Take the code apart

Read from the first line down

  1. Read -export([run/1]).: Expose one project entry point.
  2. Read count/1: Keep an unexported helper private.
  3. Read maps:update_with/4: Update one count in a map.
03 · Meet the new symbols

Symbols are not secret signs

-export([run/1]).

Expose one project entry point.

count/1

Keep an unexported helper private.

maps:update_with/4

Update one count in a map.

04 · Ideas inside the code

Match each name to its meaning

01

public API

A small public API gives callers fewer contracts and protects internal changes.

02

pure core

Pure functions transform supplied data without opening files or changing outside state.

03

boundary module

A boundary performs IO and turns its failures into project results.

05 · Make the idea clear

Why these forms are useful

Run the example first. Predict one result, then change one input and run it again.

A small public API gives callers fewer contracts and protects internal changes.

Pure functions transform supplied data without opening files or changing outside state.

06 · Change it yourself

Close the answer and try

Add a public total/1 function.

Practice starting point
total(Records) -> ____.

Target result: Return the list length.

Stuck? Read one hint

Use length/1 and remember to export the function.

After you run it, see one answer
One answer
total(Records) -> length(Records).
Think about it: Can callers invoke `count/1`?

No. It is not exported, so the module can change that helper without changing its public API.

Take with you

Remember these three lines

  1. 1

    Export a narrow API.

  2. 2

    Keep transformations pure.

  3. 3

    Put IO and error conversion at boundaries.

Lesson completeRun the code once, then mark this lesson as done.