{"article":{"slug":"embedding-eyg-in-erlang-programs","title":"Embedding EYG in Erlang programs","subtitle":null,"summary":"Step-by-step guide to embedding EYG scripting in an Erlang application: installing the toolkit, using EYG types, effects and packages from Erlang, and type-checking scripts before running them to give users or agents a tightly controlled scripting environment.","content_type":"tutorial","language":"en","canonical_url":"https://eyg.run/guides/embedding-eyg-in-erlang-programs","author":{"name":null,"url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"EYG","url":"https://eyg.run/","listing_slug":null,"listing":null},"topics":[{"name":"Tutorials","slug":"tutorials","url":"https://listedarticles.com/topics/tutorials"},{"name":"Programming Languages","slug":"programming-languages","url":"https://listedarticles.com/topics/programming-languages"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":1043,"reading_minutes":5,"published_at":"2026-10-22T02:14:39.673Z","added_at":"2026-10-22T02:14:39.673Z","updated_at":"2026-10-22T02:14:39.673Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/embedding-eyg-in-erlang-programs","markdown_url":"https://listedarticles.com/articles/embedding-eyg-in-erlang-programs.md","example":false,"citation":"EYG. \"Embedding EYG in Erlang programs.\" 22 Oct 2026. https://eyg.run/guides/embedding-eyg-in-erlang-programs (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://eyg.run/guides/embedding-eyg-in-erlang-programs"},"body_markdown":"# Embedding EYG in Erlang programs\n\nThis guide adds EYG scripting to an Erlang application `my_counters`.\nJust want the code look at [example](https://eyg.run/examples/my_counters_erlang).\n\nEmbedding EYG in Erlang gives you a scripting environment with tight controls over what can be accessed.\nWith this control you can:\n\n- Expose scripting to the users, maybe for small queries or automations\n- Build a safer shell with predefined dev tasks\n- Provide a set of business logic functions to agents\n\n## Install dependencies\n\nEYG's toolkit consists of a set of composable parts, for this guide install the following.\n\n- [eyg\\_parser](https://hex.pm/packages/eyg_parser)\n- [eyg\\_interpreter](https://hex.pm/packages/eyg_interpreter)\n- [eyg\\_analysis](https://hex.pm/packages/eyg_analysis)\n\n## Run a pure program\n\nBelow is the simplest Erlang module that will run EYG code.\n\n```\n-module(my_counters_script).\n\n-export([run/1]).\n\nrun(Code) when is_binary(Code) ->\n  case eyg@parser:all_from_string(Code) of\n    {ok, Source} -> \n      Result = eyg@interpreter@expression:execute(Source, []),\n      Result;\n    {error, Reason} -> {error, Reason}\n  end.\n```\n\nStart an Erlang shell, with this module loaded, and run your first scripts.\n\n```\n(counters@MyComputer)1> my_counters_script:run(<<\"!int_add(2, 1)\">>).\n{ok,{integer,3}}\n```\n\nThis is everything needed to run any self-contained, pure EYG program.\nWe will add two affordances that make it easier to work with.\n\nFirst we will pretty print the errors.\nEach EYG package has its own pretty printing functions.\nThe code below will handle formatting parser errors as well as runtime values and errors.\nThe return value from this function is now a binary that can be presented to the user.\n\n```\nrun(Code) when is_binary(Code) ->\n  case eyg@parser:all_from_string(Code) of\n    {ok, Source} -> \n      Result = eyg@interpreter@expression:execute(Source, []),\n      case Result of\n        {ok, Value} -> debug_interpreter_value(Value);\n        {error, Reason} -> debug_interpreter_error(Reason, Code)\n      end;\n    {error, Reason} -> debug_parser_error(Reason, Code)\n  end.\n\ndebug_parser_error(Reason, Code) ->\n  eyg@parser:format_error(Reason, Code).\n\ndebug_interpreter_value(Value) ->\n  eyg@interpreter@simple_debug:inspect(Value).\n\ndebug_interpreter_error({Reason, Span, _, _}, Code) ->\n  eyg@parser:render_error(\n    eyg@interpreter@simple_debug:describe(Reason),\n    eyg@interpreter@simple_debug:hint(Reason),\n    Code,\n    Span\n  ).\n```\n\nAll the examples in this guide we are running from the erlang shell so it's convenient for our run function to accept charlists as well as binaries.\n\n```\nrun(Code) when is_list(Code) ->\n    run(unicode:characters_to_binary(Code));\nrun(Code) when is_binary(Code) ->\n  % existing body\n```\n\nNow a runtime errors prints useful output.\n\n```\n(counters@MyComputer)1> io:put_chars(my_counters_script:run(<<\"!int_add(2, foo)\">>)).\nerror: variable undefined: foo\nhint: check the variable name or add a `let` binding before it is used\n\n 1 | !int_add(2, foo)\n                 ^^^\n```\n\n## Effects\n\nEYG programs in this first runner cannot perform any side effect.\nThis is already good enough for a configuration script but for anything more we need to add effects.\nRunning a simple hello world currently fails.\n\n```\nperform Log(\"Hello, world!\")\n```\n\n```\n(counters@MyComputer)1> io:put_chars(my_counters_script:run(<<\"perform Log(\\\"Hello, World!\\\")\">>)).\nerror: unhandled effect Log(\"Hello, World!\")\nhint: handle this effect or run the program in a runtime that supports it\n\n 1 | perform Log(\"Hello, World!\")\n     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n```\n\nAny error interpreting an EYG program returns the reason for the break and the full program state.\nAn unhandled effect is one type of break.\n\n**For an explanation of what an algebraic effect is check out this [post](https://crowdhailer.me/2025-02-14/algebraic-effects-are-a-functional-approach-to-manage-side-effects/)**\n\nWhen execution stops at an unhandled effect, the Erlang host can implement it and resume the EYG program with the reply value.\nWe update the `run` function so the initial result from executing the expression is passed to a `loop` function that will keep running the program for every break that can be handled.\n\n```\nrun(Code) when is_binary(Code) ->\n  Output = case eyg@parser:all_from_string(Code) of\n    {ok, Source} -> \n      Result = loop(eyg@interpreter@expression:execute(Source, [])),\n      % same as before\n```\n\nThe implementation of `loop` matches on an `unhandled_effect`.\nIf the extrinsic implementation of the effect succeeds the loop recurses.\n\n```\n-include_lib(\"eyg_interpreter/include/eyg@interpreter@break_UnhandledEffect.hrl\").\n-include_lib(\"eyg_interpreter/include/eyg@interpreter@value_String.hrl\").\n\nloop({error, {#unhandled_effect{label = Label, value = Value}, Span, Env, Stack}}) ->\n  case extrinsic(Label, Value) of\n    {ok, Reply} ->\n      loop(eyg@interpreter@expression:resume(Reply, Env, Stack));\n    {error, Reason} ->\n      {error, {Reason, Span, Env, Stack}}\n  end;\nloop(Result) -> Result.\n\nextrinsic(<<\"Log\">>, #string{value = String})->\n  io:put_chars(String),\n  {ok, eyg@interpreter@value:unit()};\nextrinsic(Label, Value) -> {error, #unhandled_effect{label = Label, value = Value}}.\n```\n\nEffects can be implemented at any level of abstraction.\nThey can be core utilities like logging or more specific to the application.\nThe counter example has additional utilities `Random` and `Now` as well as three effects for managing the counter application.\n\n## Type checking\n\nBefore running an EYG we can check that it has no runtime errors.\nThe type inferences is sound, any program that passes type checking has no runtime errors.\nAn effect context is passed to type checking ensuring that a program has no errors including trying to call effects that are unavailable.\n\nIn this type checking loop we start with a pure context and extend it with just the the `Log` effect.\n\n```\ntype_check(Source) ->\n  Infer = eyg@analysis@inference@levels_j@contextual,\n  Analysis = type_check_loop(Infer:check(inference_context(), Source)),\n  Infer:all_errors(Analysis).\n\ntype_check_loop({done, Analysis}) -> Analysis;\ntype_check_loop({lookup, _Reference, Resume}) -> type_check_loop(Resume({error, nil})).\n\ninference_context() ->\n  Infer = eyg@analysis@inference@levels_j@contextual,\n  Context0 = Infer:pure(),\n  Unit = eyg@analysis@type_@isomorphic:record([]),\n  Context1 = Infer:with_effect(Context0, <<\"Log\">>, string, Unit),\n  Context1.\n\ndebug_type_error({Span, Reason}, Source) ->\n  eyg@parser:render_error(\n      eyg@analysis@type_@binding@debug:render_reason(Reason),\n      eyg@analysis@type_@binding@debug:hint(Reason),\n      Source,\n      Span\n  ).\n```\n\nReplace the successful parse branch in `run/1` with the following.\nIf type checking succeeds, run the program; otherwise, return the formatted diagnostics.\n\n```\n{ok, Source} ->\n  case type_check(Source) of\n    [] ->\n      Result = loop(eyg@interpreter@expression:execute(Source, [])),\n      case Result of\n        {ok, Value} -> debug_interpreter_value(Value);\n        {error, Reason} -> debug_interpreter_error(Reason, Code)\n      end;\n    Errors ->\n      unicode:characters_to_binary([[debug_type_error(Error, Code), \"\\n\"] || Error <- Errors])\n  end;\n```\n\nYou now have an EYG scripting environment that checks programs before running them and has the full power of Erlang available for implementing effects.\nTo expose more of your application, add effect handlers and their corresponding types to the inference context.\n","body_html":"<h1 id=\"embedding-eyg-in-erlang-programs\">Embedding EYG in Erlang programs</h1>\n<p>This guide adds EYG scripting to an Erlang application <code>my_counters</code>.\nJust want the code look at <a href=\"https://eyg.run/examples/my_counters_erlang\" rel=\"nofollow ugc noopener\">example</a>.</p>\n<p>Embedding EYG in Erlang gives you a scripting environment with tight controls over what can be accessed.\nWith this control you can:</p>\n<ul><li>Expose scripting to the users, maybe for small queries or automations</li><li>Build a safer shell with predefined dev tasks</li><li>Provide a set of business logic functions to agents</li></ul>\n<h2 id=\"install-dependencies\">Install dependencies</h2>\n<p>EYG&#39;s toolkit consists of a set of composable parts, for this guide install the following.</p>\n<ul><li><a href=\"https://hex.pm/packages/eyg_parser\" rel=\"nofollow ugc noopener\">eyg_parser</a></li><li><a href=\"https://hex.pm/packages/eyg_interpreter\" rel=\"nofollow ugc noopener\">eyg_interpreter</a></li><li><a href=\"https://hex.pm/packages/eyg_analysis\" rel=\"nofollow ugc noopener\">eyg_analysis</a></li></ul>\n<h2 id=\"run-a-pure-program\">Run a pure program</h2>\n<p>Below is the simplest Erlang module that will run EYG code.</p>\n<pre><code>-module(my_counters_script).\n\n-export([run/1]).\n\nrun(Code) when is_binary(Code) -&gt;\n  case eyg@parser:all_from_string(Code) of\n    {ok, Source} -&gt; \n      Result = eyg@interpreter@expression:execute(Source, []),\n      Result;\n    {error, Reason} -&gt; {error, Reason}\n  end.</code></pre>\n<p>Start an Erlang shell, with this module loaded, and run your first scripts.</p>\n<pre><code>(counters@MyComputer)1&gt; my_counters_script:run(&lt;&lt;&quot;!int_add(2, 1)&quot;&gt;&gt;).\n{ok,{integer,3}}</code></pre>\n<p>This is everything needed to run any self-contained, pure EYG program.\nWe will add two affordances that make it easier to work with.</p>\n<p>First we will pretty print the errors.\nEach EYG package has its own pretty printing functions.\nThe code below will handle formatting parser errors as well as runtime values and errors.\nThe return value from this function is now a binary that can be presented to the user.</p>\n<pre><code>run(Code) when is_binary(Code) -&gt;\n  case eyg@parser:all_from_string(Code) of\n    {ok, Source} -&gt; \n      Result = eyg@interpreter@expression:execute(Source, []),\n      case Result of\n        {ok, Value} -&gt; debug_interpreter_value(Value);\n        {error, Reason} -&gt; debug_interpreter_error(Reason, Code)\n      end;\n    {error, Reason} -&gt; debug_parser_error(Reason, Code)\n  end.\n\ndebug_parser_error(Reason, Code) -&gt;\n  eyg@parser:format_error(Reason, Code).\n\ndebug_interpreter_value(Value) -&gt;\n  eyg@interpreter@simple_debug:inspect(Value).\n\ndebug_interpreter_error({Reason, Span, _, _}, Code) -&gt;\n  eyg@parser:render_error(\n    eyg@interpreter@simple_debug:describe(Reason),\n    eyg@interpreter@simple_debug:hint(Reason),\n    Code,\n    Span\n  ).</code></pre>\n<p>All the examples in this guide we are running from the erlang shell so it&#39;s convenient for our run function to accept charlists as well as binaries.</p>\n<pre><code>run(Code) when is_list(Code) -&gt;\n    run(unicode:characters_to_binary(Code));\nrun(Code) when is_binary(Code) -&gt;\n  % existing body</code></pre>\n<p>Now a runtime errors prints useful output.</p>\n<pre><code>(counters@MyComputer)1&gt; io:put_chars(my_counters_script:run(&lt;&lt;&quot;!int_add(2, foo)&quot;&gt;&gt;)).\nerror: variable undefined: foo\nhint: check the variable name or add a `let` binding before it is used\n\n 1 | !int_add(2, foo)\n                 ^^^</code></pre>\n<h2 id=\"effects\">Effects</h2>\n<p>EYG programs in this first runner cannot perform any side effect.\nThis is already good enough for a configuration script but for anything more we need to add effects.\nRunning a simple hello world currently fails.</p>\n<pre><code>perform Log(&quot;Hello, world!&quot;)</code></pre>\n<pre><code>(counters@MyComputer)1&gt; io:put_chars(my_counters_script:run(&lt;&lt;&quot;perform Log(\\&quot;Hello, World!\\&quot;)&quot;&gt;&gt;)).\nerror: unhandled effect Log(&quot;Hello, World!&quot;)\nhint: handle this effect or run the program in a runtime that supports it\n\n 1 | perform Log(&quot;Hello, World!&quot;)\n     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^</code></pre>\n<p>Any error interpreting an EYG program returns the reason for the break and the full program state.\nAn unhandled effect is one type of break.</p>\n<p><strong>For an explanation of what an algebraic effect is check out this <a href=\"https://crowdhailer.me/2025-02-14/algebraic-effects-are-a-functional-approach-to-manage-side-effects/\" rel=\"nofollow ugc noopener\">post</a></strong></p>\n<p>When execution stops at an unhandled effect, the Erlang host can implement it and resume the EYG program with the reply value.\nWe update the <code>run</code> function so the initial result from executing the expression is passed to a <code>loop</code> function that will keep running the program for every break that can be handled.</p>\n<pre><code>run(Code) when is_binary(Code) -&gt;\n  Output = case eyg@parser:all_from_string(Code) of\n    {ok, Source} -&gt; \n      Result = loop(eyg@interpreter@expression:execute(Source, [])),\n      % same as before</code></pre>\n<p>The implementation of <code>loop</code> matches on an <code>unhandled_effect</code>.\nIf the extrinsic implementation of the effect succeeds the loop recurses.</p>\n<pre><code>-include_lib(&quot;eyg_interpreter/include/eyg@interpreter@break_UnhandledEffect.hrl&quot;).\n-include_lib(&quot;eyg_interpreter/include/eyg@interpreter@value_String.hrl&quot;).\n\nloop({error, {#unhandled_effect{label = Label, value = Value}, Span, Env, Stack}}) -&gt;\n  case extrinsic(Label, Value) of\n    {ok, Reply} -&gt;\n      loop(eyg@interpreter@expression:resume(Reply, Env, Stack));\n    {error, Reason} -&gt;\n      {error, {Reason, Span, Env, Stack}}\n  end;\nloop(Result) -&gt; Result.\n\nextrinsic(&lt;&lt;&quot;Log&quot;&gt;&gt;, #string{value = String})-&gt;\n  io:put_chars(String),\n  {ok, eyg@interpreter@value:unit()};\nextrinsic(Label, Value) -&gt; {error, #unhandled_effect{label = Label, value = Value}}.</code></pre>\n<p>Effects can be implemented at any level of abstraction.\nThey can be core utilities like logging or more specific to the application.\nThe counter example has additional utilities <code>Random</code> and <code>Now</code> as well as three effects for managing the counter application.</p>\n<h2 id=\"type-checking\">Type checking</h2>\n<p>Before running an EYG we can check that it has no runtime errors.\nThe type inferences is sound, any program that passes type checking has no runtime errors.\nAn effect context is passed to type checking ensuring that a program has no errors including trying to call effects that are unavailable.</p>\n<p>In this type checking loop we start with a pure context and extend it with just the the <code>Log</code> effect.</p>\n<pre><code>type_check(Source) -&gt;\n  Infer = eyg@analysis@inference@levels_j@contextual,\n  Analysis = type_check_loop(Infer:check(inference_context(), Source)),\n  Infer:all_errors(Analysis).\n\ntype_check_loop({done, Analysis}) -&gt; Analysis;\ntype_check_loop({lookup, _Reference, Resume}) -&gt; type_check_loop(Resume({error, nil})).\n\ninference_context() -&gt;\n  Infer = eyg@analysis@inference@levels_j@contextual,\n  Context0 = Infer:pure(),\n  Unit = eyg@analysis@type_@isomorphic:record([]),\n  Context1 = Infer:with_effect(Context0, &lt;&lt;&quot;Log&quot;&gt;&gt;, string, Unit),\n  Context1.\n\ndebug_type_error({Span, Reason}, Source) -&gt;\n  eyg@parser:render_error(\n      eyg@analysis@type_@binding@debug:render_reason(Reason),\n      eyg@analysis@type_@binding@debug:hint(Reason),\n      Source,\n      Span\n  ).</code></pre>\n<p>Replace the successful parse branch in <code>run/1</code> with the following.\nIf type checking succeeds, run the program; otherwise, return the formatted diagnostics.</p>\n<pre><code>{ok, Source} -&gt;\n  case type_check(Source) of\n    [] -&gt;\n      Result = loop(eyg@interpreter@expression:execute(Source, [])),\n      case Result of\n        {ok, Value} -&gt; debug_interpreter_value(Value);\n        {error, Reason} -&gt; debug_interpreter_error(Reason, Code)\n      end;\n    Errors -&gt;\n      unicode:characters_to_binary([[debug_type_error(Error, Code), &quot;\\n&quot;] || Error &lt;- Errors])\n  end;</code></pre>\n<p>You now have an EYG scripting environment that checks programs before running them and has the full power of Erlang available for implementing effects.\nTo expose more of your application, add effect handlers and their corresponding types to the inference context.</p>","headings":[{"level":1,"text":"Embedding EYG in Erlang programs","id":"embedding-eyg-in-erlang-programs"},{"level":2,"text":"Install dependencies","id":"install-dependencies"},{"level":2,"text":"Run a pure program","id":"run-a-pure-program"},{"level":2,"text":"Effects","id":"effects"},{"level":2,"text":"Type checking","id":"type-checking"}]}}