Repository: wojtekmach/oop
Branch: master
Commit: b1ce8bf1dff7
Files: 7
Total size: 14.0 KB
Directory structure:
gitextract_t24ek6r3/
├── .gitignore
├── .travis.yml
├── README.md
├── lib/
│ └── oop.ex
├── mix.exs
└── test/
├── oop_test.exs
└── test_helper.exs
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
/_build
/cover
/deps
erl_crash.dump
*.ez
================================================
FILE: .travis.yml
================================================
language: elixir
sudo: false
elixir:
- 1.3.4
otp_release:
- 18.2
================================================
FILE: README.md
================================================
<h1 align="center"> <br><img src="logo/logotype_horizontal.png?raw=true" alt="oop" width="256"> <br>
# OOP
[](https://travis-ci.org/wojtekmach/oop)
Are you tired of all of that modules, processes and functions nonsense? Do you want to just use classes, objects and methods? If so, use OOP [1] library in Elixir [2]!
## Demo
[](https://www.youtube.com/watch?v=5EtV2JUU0Z4)
## Example
```elixir
import OOP
class Person do
var :name
def say_hello_to(who) do
what = "Hello #{who.name}"
IO.puts("#{this.name}: #{what}")
end
end
joe = Person.new(name: "Joe")
mike = Person.new(name: "Mike")
robert = Person.new(name: "Robert")
joe.say_hello_to(mike) # Joe: Hello Mike
mike.say_hello_to(joe) # Mike: Hello Joe
mike.say_hello_to(robert) # Mike: Hello Robert
robert.say_hello_to(mike) # Robert: Hello Mike
joe.set_name("Hipster Joe")
joe.name # => Hipster Joe
```
An OOP library wouldn't be complete without inheritance:
```elixir
class Animal do
var :name
end
class Dog < Animal do
var :breed
end
snuffles = Dog.new(name: "Snuffles", breed: "Shih Tzu")
snuffles.name # => "Snuffles"
snuffles.breed # => "Shih Tzu"
```
... or multiple inheritance:
```elixir
class Human do
var :name
end
class Horse do
var :horseshoes_on?
end
class Centaur < [Human, Horse] do
end
john = Centaur.new(name: "John", horseshoes_on?: true)
john.name # => "John"
john.horseshoes_on? # => true
```
See more usage in the [test suite](test/oop_test.exs).
## Installation
Add `oop` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[{:oop, "~> 0.1.0"}]
end
```
[1] According to Alan Kay, the inventor of OOP, "objects" is the lesser idea; the big idea is "messaging". In that sense, I can't agree more with Joe Armstrong's quote that Erlang is "possibly the only object-oriented language".
[2] Please don't. You've been warned.
## License
The MIT License (MIT)
Copyright (c) 2015 Wojciech Mach
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: lib/oop.ex
================================================
defmodule OOP.Registry do
def start_link do
Agent.start_link(fn -> %{} end, name: __MODULE__)
end
def register(pid, class) do
Agent.update(__MODULE__, &Map.put(&1, pid, class))
end
def get(pid) do
Agent.get(__MODULE__, &Map.get(&1, pid, nil))
end
end
defmodule OOP.Application do
use Application
def start(_type, _args) do
OOP.Registry.start_link()
end
end
defmodule OOP.Builder do
def create_class(class, superclasses, block, opts) do
quote do
defmodule unquote(class) do
OOP.Builder.ensure_can_be_subclassed(unquote(superclasses))
@final Keyword.get(unquote(opts), :final, false)
def __final__? do
@final
end
def new(data \\ [], descendant? \\ false) do
OOP.Builder.ensure_can_be_instantiated(unquote(class), descendant?, unquote(opts))
object = :"#{unquote(class)}#{:erlang.unique_integer()}"
defmodule object do
use GenServer
def start_link(data) do
GenServer.start_link(__MODULE__, data, name: __MODULE__)
end
def class do
unquote(class)
end
def methods do
built_ins = [
code_change: 3, handle_call: 3, handle_cast: 2, handle_info: 2,
init: 1, start_link: 1, terminate: 2,
class: 0, methods: 0,
]
__MODULE__.__info__(:functions) -- built_ins
end
import Kernel, except: [def: 2]
Module.register_attribute(__MODULE__, :friends, accumulate: true)
unquote(block)
Enum.each(unquote(superclasses), fn superclass ->
parent = superclass.new(data, true)
for {method, arity} <- parent.methods do
Code.eval_quoted(OOP.Builder.inherit_method(method, arity, parent), [], __ENV__)
end
end)
end
{:ok, pid} = object.start_link(Enum.into(data, %{}))
OOP.Registry.register(pid, unquote(class))
object
end
end
end
end
def ensure_can_be_subclassed(superclasses) do
Enum.each(superclasses, fn s ->
if s.__final__?, do: raise "cannot subclass final class #{s}"
end)
end
def ensure_can_be_instantiated(class, descendant?, opts) do
abstract? = Keyword.get(opts, :abstract, false)
if !descendant? and abstract? do
raise "cannot instantiate abstract class #{class}"
end
end
def create_method(call, expr) do
# HACK: this is a really gross way of checking if the function is using `this`.
# if so, we let it leak: `var!(this) = data`.
# We do this so that we don't get the "unused variable this" warning when
# we don't use `this`.
using_this? = String.match?(Macro.to_string(expr), ~r"\bthis\.")
{method, args} = Macro.decompose_call(call)
handle_call_quoted =
quote do
try do
[do: value] = unquote(expr)
{:reply, {:ok, value}, data}
rescue
e in [RuntimeError] ->
{:reply, {:error, e}, data}
end
end
quote do
def unquote(call) do
case GenServer.call(__MODULE__, {:call, unquote(method), unquote(args)}) do
{:ok, value} -> value
{:error, e} -> raise e
end
end
if unquote(using_this?) do
def handle_call({:call, unquote(method), unquote(args)}, _from, data) do
var!(this) = data
unquote(handle_call_quoted)
end
else
def handle_call({:call, unquote(method), unquote(args)}, _from, data) do
unquote(handle_call_quoted)
end
end
end
end
def inherit_method(method, arity, parent) do
args = (0..arity) |> Enum.drop(1) |> Enum.map(fn i -> {:"arg#{i}", [], OOP} end)
{:defdelegate, [context: OOP, import: Kernel],
[{method, [], args}, [to: parent]]}
end
def create_var(field, opts) do
private? = Keyword.get(opts, :private, false)
quote do
def unquote(field)() do
case GenServer.call(__MODULE__, {:get, unquote(field)}) do
{:ok, value} -> value
{:error, :private} -> raise "Cannot access private var #{unquote(field)}"
end
end
def unquote(:"set_#{field}")(value) do
GenServer.call(__MODULE__, {:set, unquote(field), value})
end
def handle_call({:get, unquote(field)}, {pid, _ref}, data) do
classes = [class() | @friends]
if unquote(private?) and ! OOP.Registry.get(pid) in classes do
{:reply, {:error, :private}, data}
else
{:reply, {:ok, Map.get(data, unquote(field))}, data}
end
end
def handle_call({:set, unquote(field), value}, _from, data) do
{:reply, value, Map.put(data, unquote(field), value)}
end
end
end
end
defmodule OOP do
defmacro class(class_expr, block, opts \\ []) do
{class, superclasses} =
case class_expr do
{:<, _, [class, superclasses]} when is_list(superclasses) ->
{class, superclasses}
{:<, _, [class, superclass]} ->
{class, [superclass]}
class ->
{class, []}
end
OOP.Builder.create_class(class, superclasses, block, opts)
end
defmacro abstract(class_expr, block) do
{:class, _, [class]} = class_expr
quote do
OOP.class(unquote(class), unquote(block), abstract: true)
end
end
defmacro final(class_expr, block) do
{:class, _, [class]} = class_expr
quote do
OOP.class(unquote(class), unquote(block), final: true)
end
end
defmacro def(call, expr \\ nil) do
OOP.Builder.create_method(call, expr)
end
defmacro var(field, opts \\ []) do
OOP.Builder.create_var(field, opts)
end
defmacro private_var(field) do
quote do
var(unquote(field), private: true)
end
end
defmacro friend(class) do
quote do
@friends unquote(class)
end
end
end
================================================
FILE: mix.exs
================================================
defmodule OOP.Mixfile do
use Mix.Project
def project do
[app: :oop,
version: "0.1.1",
description: "OOP in Elixir!",
package: package(),
elixir: "~> 1.0",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps()]
end
def application do
[mod: {OOP.Application, []}]
end
defp deps do
[]
end
defp package do
[
maintainers: ["Wojtek Mach"],
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/wojtekmach/oop"},
]
end
end
================================================
FILE: test/oop_test.exs
================================================
defmodule OOPTest do
use ExUnit.Case
import OOP
test "define empty class" do
c = class Person do
end
assert c
purge Person
end
test "instantiate empty object" do
class Person do
end
alice = Person.new
assert alice.class == Person
purge Person
end
test "define methods on objects" do
class Person do
def zero do
0
end
def sum1(a) do
a
end
def sum2(a, b) do
a + b
end
end
alice = Person.new
assert alice.zero() == 0
assert alice.sum1(1) == 1
assert alice.sum2(1, 2) == 3
purge Person
end
test "define fields" do
class Person do
var :name
def title(prefix) do
"#{prefix} #{this.name}"
end
end
alice = Person.new
assert alice.name == nil
bob = Person.new(name: "Bob")
assert bob.name == "Bob"
bob.set_name("Hipster Bob")
assert bob.name == "Hipster Bob"
assert bob.title("Mr.") == "Mr. Hipster Bob"
assert alice.name == nil
purge Person
end
test "define private fields" do
class AppleInc do
private_var :registered_devices
def registered_devices_count do
length(this.registered_devices)
end
end
apple = AppleInc.new(registered_devices: ["Alice's iPhone", "Bob's iPhone"])
assert_raise RuntimeError, "Cannot access private var registered_devices", fn ->
apple.registered_devices
end
assert apple.registered_devices_count == 2
purge AppleInc
end
test "define friend class" do
class NSA do
def get_data(company) do
company.registered_devices
end
end
class Thief do
def get_data(company) do
company.registered_devices
end
end
class AppleInc do
friend NSA
private_var :registered_devices
end
apple = AppleInc.new(registered_devices: ["Alice's iPhone", "Bob's iPhone"])
thief = Thief.new
nsa = NSA.new
assert_raise RuntimeError, "Cannot access private var registered_devices", fn ->
thief.get_data(apple)
end
assert nsa.get_data(apple) == ["Alice's iPhone", "Bob's iPhone"]
purge [AppleInc, Thief, NSA]
end
test "inheritance" do
class Animal do
var :name
def title(prefix) do
"#{prefix} #{this.name}"
end
end
class Dog < Animal do
var :breed
end
snuffles = Dog.new(name: "Snuffles", breed: "Shih Tzu")
assert snuffles.name == "Snuffles"
assert snuffles.breed == "Shih Tzu"
assert snuffles.title("Mr.") == "Mr. Snuffles"
purge [Animal, Dog]
end
test "multiple inheritance" do
class Human do
var :name
end
class Horse do
var :horseshoes_on?
end
class Centaur < [Human, Horse] do
end
john = Centaur.new(name: "John", horseshoes_on?: true)
assert john.name == "John"
assert john.horseshoes_on? == true
purge [Human, Horse, Centaur]
end
test "define abstract class" do
abstract class ActiveRecord.Base do
end
assert_raise RuntimeError, "cannot instantiate abstract class #{ActiveRecord.Base}", fn ->
ActiveRecord.Base.new
end
class Post < ActiveRecord.Base do
var :title
end
assert Post.new(title: "Post 1").title == "Post 1"
purge [ActiveRecord.Base, Post]
end
test "abstract class inheriting from abstract class" do
abstract class ActiveRecord.Base do
end
abstract class ApplicationRecord < ActiveRecord.Base do
end
assert_raise RuntimeError, "cannot instantiate abstract class #{ActiveRecord.Base}", fn ->
ActiveRecord.Base.new
end
assert_raise RuntimeError, "cannot instantiate abstract class #{ApplicationRecord}", fn ->
ApplicationRecord.new
end
class Post < ApplicationRecord do
var :title
end
assert Post.new(title: "Post 1").title == "Post 1"
purge [ActiveRecord.Base, ApplicationRecord, Post]
end
test "define final class" do
final class FriezaFourthForm do
end
assert FriezaFourthForm.new
assert_raise RuntimeError, "cannot subclass final class #{FriezaFourthForm}", fn ->
class FriezaFifthForm < FriezaFourthForm do
end
end
purge [FriezaFourthForm, FriezaFifthForm]
end
defp purge(module) when is_atom(module) do
:code.delete(module)
:code.purge(module)
end
defp purge(modules) when is_list(modules) do
Enum.each(modules, &purge/1)
end
end
================================================
FILE: test/test_helper.exs
================================================
ExUnit.start()
gitextract_t24ek6r3/
├── .gitignore
├── .travis.yml
├── README.md
├── lib/
│ └── oop.ex
├── mix.exs
└── test/
├── oop_test.exs
└── test_helper.exs
SYMBOL INDEX (20 symbols across 3 files)
FILE: lib/oop.ex
class OOP (line 179) | defmodule OOP
class OOP.Registry (line 1) | defmodule OOP.Registry
method start_link (line 2) | def start_link do
method register (line 6) | def register(pid, class) do
method get (line 10) | def get(pid) do
class OOP.Application (line 15) | defmodule OOP.Application
method start (line 18) | def start(_type, _args) do
class OOP.Builder (line 23) | defmodule OOP.Builder
method create_class (line 24) | def create_class(class, superclasses, block, opts) do
method ensure_can_be_subclassed (line 86) | def ensure_can_be_subclassed(superclasses) do
method ensure_can_be_instantiated (line 92) | def ensure_can_be_instantiated(class, descendant?, opts) do
method create_method (line 100) | def create_method(call, expr) do
method inherit_method (line 141) | def inherit_method(method, arity, parent) do
method create_var (line 148) | def create_var(field, opts) do
FILE: mix.exs
class OOP.Mixfile (line 1) | defmodule OOP.Mixfile
method project (line 4) | def project do
method application (line 15) | def application do
method deps (line 19) | defp deps do
method package (line 23) | defp package do
FILE: test/oop_test.exs
class OOPTest (line 1) | defmodule OOPTest
Condensed preview — 7 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (15K chars).
[
{
"path": ".gitignore",
"chars": 41,
"preview": "/_build\n/cover\n/deps\nerl_crash.dump\n*.ez\n"
},
{
"path": ".travis.yml",
"chars": 65,
"preview": "language: elixir\nsudo: false\nelixir:\n- 1.3.4\notp_release:\n- 18.2\n"
},
{
"path": "README.md",
"chars": 3127,
"preview": "<h1 align=\"center\"> <br><img src=\"logo/logotype_horizontal.png?raw=true\" alt=\"oop\" width=\"256\"> <br>\n\n# OOP\n\n[![Build St"
},
{
"path": "lib/oop.ex",
"chars": 6037,
"preview": "defmodule OOP.Registry do\n def start_link do\n Agent.start_link(fn -> %{} end, name: __MODULE__)\n end\n\n def registe"
},
{
"path": "mix.exs",
"chars": 545,
"preview": "defmodule OOP.Mixfile do\n use Mix.Project\n\n def project do\n [app: :oop,\n version: \"0.1.1\",\n description: \"O"
},
{
"path": "test/oop_test.exs",
"chars": 4487,
"preview": "defmodule OOPTest do\n use ExUnit.Case\n import OOP\n\n test \"define empty class\" do\n c = class Person do\n end\n\n "
},
{
"path": "test/test_helper.exs",
"chars": 15,
"preview": "ExUnit.start()\n"
}
]
About this extraction
This page contains the full source code of the wojtekmach/oop GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 7 files (14.0 KB), approximately 4.1k tokens, and a symbol index with 20 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.