{"id":19691498,"url":"https://github.com/kbrw/clojure-erlastic","last_synced_at":"2025-12-12T01:33:31.561Z","repository":{"id":62431607,"uuid":"20801063","full_name":"kbrw/clojure-erlastic","owner":"kbrw","description":"Micro lib making use of erlang JInterface lib to decode and encode Binary Erlang Term and simple erlang port interface with core.async channel. So you can communicate with erlang coroutine with clojure abstraction","archived":false,"fork":false,"pushed_at":"2017-10-21T10:16:53.000Z","size":110,"stargazers_count":34,"open_issues_count":2,"forks_count":6,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-25T03:47:05.217Z","etag":null,"topics":["clojure","erlang-ports"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kbrw.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-06-13T10:42:05.000Z","updated_at":"2024-05-31T07:57:20.000Z","dependencies_parsed_at":"2022-11-01T20:46:36.238Z","dependency_job_id":null,"html_url":"https://github.com/kbrw/clojure-erlastic","commit_stats":null,"previous_names":["awetzel/clojure-erlastic"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kbrw%2Fclojure-erlastic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kbrw%2Fclojure-erlastic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kbrw%2Fclojure-erlastic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kbrw%2Fclojure-erlastic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kbrw","download_url":"https://codeload.github.com/kbrw/clojure-erlastic/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251473207,"owners_count":21595023,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","erlang-ports"],"created_at":"2024-11-11T19:09:31.776Z","updated_at":"2025-12-12T01:33:26.510Z","avatar_url":"https://github.com/kbrw.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"clojure-erlastic\n================\n\n![Clojars Project](http://clojars.org/clojure-erlastic/latest-version.svg)\n\nMicro lib making use of erlang JInterface lib to decode and encode Binary\nErlang Term and simple erlang port interface with core.async channel. So you\ncan communicate with erlang coroutine with clojure abstraction\n\nDesigned to be used (but not necessarily) with\n[https://github.com/awetzel/exos](https://github.com/awetzel/exos).\n\nLast version of JInterface (from erlang 17.0) is taken from google scalaris\nmaven repo. \n\n## Usage\n\n`port-connection` creates two channels that you can use to\ncommunicate respectively in and out with the calling erlang port.\nThe objects you put or receive throught these channels are encoded\nand decoded into erlang binary term following these rules :\n\n- erlang atom is clojure keyword\n- erlang list is clojure list\n- erlang tuple is clojure vector\n- erlang binary is clojure bytes[]\n- erlang integer is clojure long\n- erlang float is clojure double\n- erlang map is clojure map (thanks to erlang 17.0)\n- clojure set is erlang list\n\nConversion of nil and string are configurable : every functions\n`port-connection`, `decode`, `encode`, `run-server` can take an optional\n`config` argument : a map defining 3 configs `:convention`, `:str-detect`, `:str-autodetect-len`.\n\n- if `(= :convention :elixir)` then : \n  - clojure nil is erlang `nil` atom, so elixir `nil`\n  - clojure string is encoded into erlang utf8 binary\n  - erlang binaries are decoded into clojure string :\n    - always if `(= :str-detect :all)`\n    - never if `(= :str-detect :none)`\n    - if the \"str-autodetect-len\" first bytes are printable when `(= :str-detect :auto)`\n- if `(= :convention :erlang)` then : \n  - clojure nil is erlang `undefined`\n  - clojure string is encoded into erlang integer list\n  - erlang lists are decoded into clojure string :\n    - always if `(= :str-detect :all)`\n    - never if `(= :str-detect :none)`\n    - if the \"str-autodetect-len\" first elems are printable when `(= :str-detect :auto)`\n\n- default config is Elixir convention with no str detection.\n\nFor instance, here is a simple echo server :\n\n```clojure\n(let [[in out] (clojure-erlastic.core/port-connection)]\n  (\u003c!! (go (while true\n    (\u003e! out (\u003c! in))))))\n```\n\n## Example : a simple clojure calculator ##\n\nMy advice to create a simple erlang/elixir server in clojure is to create a `project.clj` containing the clojure-erlastic dependency and other needed deps for your server, then use \"lein uberjar\" to create a jar containing all the needed files. \n\n\u003e mkdir calculator; cd calculator\n\n\u003e vim project.clj\n\n```clojure\n(defproject calculator \"0.0.1\" \n  :dependencies [[clojure-erlastic \"0.1.4\"]\n                 [org.clojure/core.match \"0.2.1\"]])\n```\n\n\u003e lein uberjar\n\nThen create your clojure server as a simple script \n\n\u003e vim calculator.clj\n\n```clojure\n(require '[clojure.core.async :as async :refer [\u003c! \u003e! \u003c!! go]])\n(require '[clojure-erlastic.core :refer [port-connection log]])\n(use '[clojure.core.match :only (match)])\n\n(let [[in out] (clojure-erlastic.core/port-connection)]\n  (\u003c!! (go \n    (loop [num 0]\n      (match (\u003c! in)\n        [:add n] (recur (+ num n))\n        [:rem n] (recur (- num n))\n        :get (do (\u003e! out num) (recur num)))))))\n```\n\nFinally launch the clojure server as a port, do not forget the `:binary` and `{:packet,4}` options, mandatory, then convert sent and received terms with `:erlang.binary_to_term` and `:erlang.term_to_binary`.\n\n\u003e vim calculator.exs\n\n```elixir\ndefmodule CljPort do\n  def start, do: \n    Port.open({:spawn,'java -cp target/calculator-0.0.1-standalone.jar clojure.main calculator.clj'},[:binary, packet: 4])\n  def psend(port,data), do: \n    send(port,{self,{:command,:erlang.term_to_binary(data)}})\n  def preceive(port), do: \n    receive(do: ({^port,{:data,b}}-\u003e:erlang.binary_to_term(b)))\nend\nport = CljPort.start\nCljPort.psend(port, {:add,3})\nCljPort.psend(port, {:rem,2})\nCljPort.psend(port, {:add,5})\nCljPort.psend(port, :get)\n6 = CljPort.preceive(port)\n```\n\n\u003e elixir calculator.exs\n\n## OTP integration ##\n\nIf you want to integrate your clojure server in your OTP application, use the\n`priv` directory which is copied 'as is'.\n\n```bash\nmix new myapp ; cd myapp\nmkdir -p priv/calculator\nvim priv/calculator/project.clj # define dependencies\nvim priv/calculator/calculator.clj # write your server\ncd priv/calculator ; lein uberjar ; cd ../../ # build the jar\n```\n\nThen use `\"#{:code.priv_dir(:myapp)}/calculator\"` to find correct path in your app.\n\nTo easily use your clojure server, link the opened port in a GenServer, to\nensure that if java crash, then the genserver crash and can be restarted by its\nsupervisor.\n\n\u003e vim lib/calculator.ex\n\n```elixir\ndefmodule Calculator do\n  use GenServer\n  def start_link, do: GenServer.start_link(__MODULE__, nil, name: __MODULE__)\n  def init(nil) do\n    Process.flag(:trap_exit, true)\n    cd = \"#{:code.priv_dir(:myapp)}/calculator\"\n    cmd = \"java -cp 'target/*' clojure.main calculator.clj\"\n    {:ok,Port.open({:spawn,'#{cmd}'},[:binary, packet: 4, cd: cd])}\n  end\n  def handle_info({:EXIT,port,_},port), do: exit(:port_terminated)\n\n  def handle_cast(term,port) do\n    send(port,{self,{:command,:erlang.term_to_binary(term)}})\n    {:noreply,port}\n  end\n\n  def handle_call(term,_,port) do\n    send(port,{self,{:command,:erlang.term_to_binary(term)}})\n    result = receive do {^port,{:data,b}}-\u003e:erlang.binary_to_term(b) end\n    {:reply,result,port}\n  end\nend\n```\n\nThen create the OTP application and its root supervisor launching `Calculator`.\n\n\u003e vim mix.exs\n\n```elixir\n  def application do\n    [mod: { Myapp, [] },\n     applications: []]\n  end\n```\n\n\u003e vim lib/myapp.ex\n\n```elixir\ndefmodule Myapp do\n  use Application\n  def start(_type, _args), do: Myapp.Sup.start_link\n\n  defmodule Sup do\n    use Supervisor\n    def start_link, do: :supervisor.start_link(__MODULE__,nil)\n    def init(nil), do:\n      supervise([worker(Calculator,[])], strategy: :one_for_one)\n  end\nend\n```\n\nThen you can launch and test your application in the shell : \n\n```\niex -S mix\niex(1)\u003e GenServer.call Calculator,:get\n0\niex(2)\u003e GenServer.cast Calculator,{:add, 3}\n:ok\niex(3)\u003e GenServer.cast Calculator,{:add, 3}\n:ok\niex(4)\u003e GenServer.cast Calculator,{:add, 3}\n:ok\niex(5)\u003e GenServer.cast Calculator,{:add, 3}\n:ok\niex(6)\u003e GenServer.call Calculator,:get\n12\n```\n## Handle exit\n\nThe channels are closed when the launching erlang application dies, so you just\nhave to test if `(\u003c! in)` is `nil` to know if the connection with erlang is\nstill opened.  \n\n## Erlang style handler ##\n\nIn Java you cannot write a function as big as you want (the compiler may fail),\nand the `go` and `match` macros expand into a lot of code. So it can be\nuseful to wrap your server with an \"erlang-style\" handler.\n\nClojure-erlastic provide the function `(run-server initfun handlefun)`\nallowing you to easily develop a server using erlang-style handler :\n\n- the `init` function must return the initial state\n- the `handle` function must return `[:reply response newstate]`, or `[:noreply newstate]`\n\nThe argument of the init function is the first message sent by the erlang port\nafter starting.\n\n```clojure\n(require '[clojure.core.async :as async :refer [\u003c! \u003e! \u003c!! go]])\n(require '[clojure-erlastic.core :refer [run-server log]])\n(use '[clojure.core.match :only (match)])\n\n(run-server\n  (fn [_] 0)\n  (fn [term state] (match term\n    [:add n] [:noreply (+ state n)]\n    [:rem n] [:noreply (- state n)]\n    :get [:reply state state]))\n  {:convention :erlang})\n\n(log \"end application, clean if necessary\")\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkbrw%2Fclojure-erlastic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkbrw%2Fclojure-erlastic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkbrw%2Fclojure-erlastic/lists"}