Though, Joe Armstrong denies that Erlang is object oriented language, but according Ralph Johnson concurrent Erlang is object oriented. Erlang is based on Actor model, where each object has a mailbox associated that receives messages. In this model, the message passing takes literal form rather than method invocation. Erlang also has strong pattern matching support, so you can achieve polymorphism by pattern matching. This kind of polymorphism is similar to duck typing rather than inheritance based polymorphism in object oriented languages. One of the hardest thing in object oriented language is to know when something can be a subclass of another class because you have to measure IS-A test or Liskov substitution principle. Most people also consider inheritance bad due to encapsulation leakage so duck typing will be easier. Since, all states are immutable in Erlang, you can’t really keep state as traditional object oriented language, but you can create a new state with every mutation and attach it to the process. For example, here is a simple example of polymorphism in Java :
1 import junit.framework.Test; 2 import junit.framework.TestCase; 3 import junit.framework.TestSuite; 4 5 import java.util.*; 6 7 8 public class PolymorTest extends TestCase { 9 Shape[] shapes; 10 11 protected void setUp() { 12 shapes = new Shape[] { 13 new Circle(3.5), new Rectangle(20, 50), new Square(10) 14 }; 15 } 16 17 protected void tearDown() { 18 } 19 20 public void testArea() { 21 assertEquals(38.484, shapes[0].area(), 0.001); 22 assertEquals(1000.0, shapes[1].area(), 0.001); 23 assertEquals(100.0, shapes[2].area(), 0.001); 24 } 25 26 public void testPerimeter() { 27 assertEquals(21.991, shapes[0].perimeter(), 0.001); 28 assertEquals(140.0, shapes[1].perimeter(), 0.001); 29 assertEquals(40.0, shapes[2].perimeter(), 0.001); 30 } 31 32 public static Test suite() { 33 TestSuite suite = new TestSuite(); 34 suite.addTestSuite(PolymorTest.class); 35 36 return suite; 37 } 38 39 public static void main(String[] args) { 40 junit.textui.TestRunner.run(suite()); 41 } 42 43 interface Shape { 44 public double area(); 45 46 public double perimeter(); 47 } 48 49 class Rectangle implements Shape { 50 double height; 51 double width; 52 53 Rectangle(double height, double width) { 54 this.height = height; 55 this.width = width; 56 } 57 58 public double area() { 59 return height * width; 60 } 61 62 public double perimeter() { 63 return (2 * height) + (2 * width); 64 } 65 66 public void setWidth(double width) { 67 this.width = width; 68 } 69 70 public double getWidth() { 71 return this.width; 72 } 73 74 public double getHeight() { 75 return this.height; 76 } 77 78 public void setHeight(double height) { 79 this.height = height; 80 } 81 } 82 83 class Square extends Rectangle { 84 Square(double side) { 85 super(side, side); 86 } 87 } 88 89 class Circle implements Shape { 90 double radius; 91 92 Circle(double radius) { 93 this.radius = radius; 94 } 95 96 public double getRadius() { 97 return this.radius; 98 } 99 100 public void setRadius(double radius) { 101 this.radius = radius; 102 } 103 104 public double area() { 105 return Math.PI * radius * radius; 106 } 107 108 public double perimeter() { 109 return Math.PI * diameter(); 110 } 111 112 public double diameter() { 113 return 2 * radius; 114 } 115 } 116 } 117
And here is Ruby equivalent, which uses duck typing:
1 require 'test/unit' 2 require 'test/unit/ui/console/testrunner' 3 4 5 class Rectangle 6 attr_accessor :height, :width 7 def initialize(height, width) 8 @height = height 9 @width = width 10 end 11 def area() 12 height*width 13 end 14 def perimeter() 15 2*height + 2*width 16 end 17 end 18 19 20 class Square 21 attr_accessor :side 22 def initialize(side) 23 @side = side 24 end 25 def area() 26 side*side 27 end 28 def perimeter() 29 4 * side 30 end 31 end 32 33 34 class Circle 35 attr_accessor :radius 36 def initialize(radius) 37 @radius = radius 38 end 39 def area() 40 Math::PI*radius*radius 41 end 42 def perimeter() 43 Math::PI*diameter() 44 end 45 def diameter() 46 2*radius 47 end 48 end 49 50 class PolymorTest < Test::Unit::TestCase 51 def setup 52 @shapes = [Circle.new(3.5), Rectangle.new(20, 50), Square.new(10)] 53 end 54 55 def test_area 56 assert_in_delta(38.484, @shapes[0].area(), 0.001, "didn't match area0 #{@shapes[0].area()}") 57 assert_in_delta(1000.0, @shapes[1].area(), 0.001, "didn't match area1 #{@shapes[1].area()}") 58 assert_in_delta(100.0, @shapes[2].area(), 0.001, "didn't match area2 #{@shapes[2].area()}") 59 end 60 def test_perimeter 61 assert_in_delta(21.991, @shapes[0].perimeter(), 0.001, "didn't match perim0 #{@shapes[0].perimeter()}") 62 assert_in_delta(140.0, @shapes[1].perimeter(), 0.001, "didn't match perim1 #{@shapes[1].perimeter()}") 63 assert_in_delta(40.0, @shapes[2].perimeter(), 0.001, "didn't match perim2 #{@shapes[2].perimeter()}") 64 end 65 end 66 67 68 Test::Unit::UI::Console::TestRunner.run(PolymorTest) 69
And here is Erlang equivalent (obviously it’s a lot more verbose):
1 -module(rectangle). 2 -compile(export_all). 3 4 5 -record(rectangle, {height, width}). 6 7 new_rectangle(H, W) -> 8 #rectangle{height = H, width = W}. 9 10 start(H, W) -> 11 Self = new_rectangle(H, W), 12 {Self, spawn(fun() -> loop(Self) end)}. 13 14 rpc(Pid, Request) -> 15 Pid ! {self(), Request}, 16 receive 17 {Pid, ok, Response} -> 18 Response 19 end. 20 21 22 area(Self) -> 23 Self#rectangle.height * Self#rectangle.width. 24 25 perimeter(Self) -> 26 2 * Self#rectangle.height + 2 * Self#rectangle.width. 27 28 set_width(Self, W) -> 29 Self#rectangle{width = W}. 30 31 get_width(Self) -> 32 Self#rectangle.width. 33 34 set_height(Self, H) -> 35 Self#rectangle{height = H}. 36 37 get_height(Self) -> 38 Self#rectangle.height. 39 40 loop(Self) -> 41 receive 42 {From, area} -> 43 From ! {self(), ok, area(Self)}, 44 loop(Self); 45 46 {From, perimeter} -> 47 From ! {self(), ok, perimeter(Self)}, 48 loop(Self); 49 50 {From, get_width} -> 51 From ! {self(), ok, get_width(Self)}, 52 loop(Self); 53 54 {From, get_height} -> 55 From ! {self(), ok, get_height(Self)}, 56 loop(Self); 57 58 {set_width, W} -> 59 loop(set_width(Self, W)); 60 61 {set_height, H} -> 62 loop(set_height(Self, H)) 63 end. 64 65 66 67 68 69 70 71 -module(circle). 72 -compile(export_all). 73 -define(PI, 3.14159). 74 75 -record(circle, {radius}). 76 77 new_circle(R) -> 78 #circle{radius = R}. 79 80 start(R) -> 81 Self = new_circle(R), 82 {Self, spawn(fun() -> loop(Self) end)}. 83 84 rpc(Pid, Request) -> 85 Pid ! {self(), Request}, 86 receive 87 {Pid, ok, Response} -> 88 Response 89 end. 90 91 92 area(Self) -> 93 ?PI * Self#circle.radius * Self#circle.radius. 94 95 perimeter(Self) -> 96 ?PI * diameter(Self). 97 98 set_radius(Self, R) -> 99 Self#circle{radius = R}. 100 101 get_radius(Self) -> 102 Self#circle.radius. 103 diameter(Self) -> 104 Self#circle.radius * 2. 105 106 107 loop(Self) -> 108 receive 109 {From, area} -> 110 From ! {self(), ok, area(Self)}, 111 loop(Self); 112 113 {From, perimeter} -> 114 From ! {self(), ok, perimeter(Self)}, 115 loop(Self); 116 117 {From, get_radius} -> 118 From ! {self(), ok, get_radius(Self)}, 119 loop(Self); 120 121 {set_radius, R} -> 122 loop(set_radius(Self, R)) 123 end. 124 125 126 127 128 129 -module(square). 130 -compile(export_all). 131 132 133 -record(square, {side}). 134 135 new_square(S) -> 136 #square{side = S}. 137 138 start(S) -> 139 Self = new_square(S), 140 {Self, spawn(fun() -> loop(Self) end)}. 141 142 rpc(Pid, Request) -> 143 Pid ! {self(), Request}, 144 receive 145 {Pid, ok, Response} -> 146 Response 147 end. 148 149 150 area(Self) -> 151 Self#square.side * Self#square.side. 152 153 perimeter(Self) -> 154 4 * Self#square.side. 155 156 set_side(Self, S) -> 157 Self#square{side = S}. 158 159 get_side(Self) -> 160 Self#square.side. 161 162 loop(Self) -> 163 receive 164 {From, area} -> 165 From ! {self(), ok, area(Self)}, 166 loop(Self); 167 168 {From, perimeter} -> 169 From ! {self(), ok, perimeter(Self)}, 170 loop(Self); 171 172 {From, get_side} -> 173 From ! {self(), ok, get_side(Self)}, 174 loop(Self); 175 176 {set_side, S} -> 177 loop(set_side(Self, S)) 178 end. 179 180 181 182 183 -module(poly_test). 184 -compile(export_all). 185 186 create() -> 187 [circle:start(3.5), rectangle:start(20, 50), square:start(10)]. 188 189 area(ShapeNPid) -> 190 {_Shape, Pid} = ShapeNPid, 191 Pid ! {self(), area}, 192 receive 193 {Pid, ok, Response} -> 194 Response 195 end. 196 197 perimeter(ShapeNPid) -> 198 {_Shape, Pid} = ShapeNPid, 199 Pid ! {self(), perimeter}, 200 receive 201 {Pid, ok, Response} -> 202 Response 203 end. 204 205 get_area(ShapesNPids) -> 206 lists:map(fun(ShapeNPid) -> area(ShapeNPid) end, ShapesNPids). 207 get_perimeter(ShapesNPids) -> 208 lists:map(fun(ShapeNPid) -> perimeter(ShapeNPid) end, ShapesNPids). 209 210 test_area() -> 211 D = create(), 212 %[CircleArea, RectangleArea, SquareArea] = [38.4845,1000,100], 213 [CircleArea, RectangleArea, SquareArea] = get_area(D). 214 215 test_perimeter() -> 216 D = create(), 217 %[CirclePerimeter, RectanglePerimeter, SquarePerimeter] = [21.9911,140,40], 218 [CirclePerimeter, RectanglePerimeter, SquarePerimeter] = get_perimeter(D). 219
Lessons Learned:
Though, Erlang supports object oriented features in its concurrent model, but it is a lot more verbose. For example, in above example Ruby was roughly 60 lines of code and Java was twice as long, whereas Erlang was about four times long.