Вы, вероятно, не заметили, но прошло пару недель с тех пор, как я в последний раз публиковал блог. Это объясняется тем, что я сломал Солей, и моя нога в гипсе. Будучи неподвижным, я подумал, что было бы неплохо исследовать что-то совершенно другое — это было либо это, либо просмотр дневного телевидения, и, хотя повторы Коджака и Магнума Пи были заманчивыми, расследование Эрланга вышло на вершину.
Важно помнить, что это не
В руководстве Erlang идея заключается в том, чтобы изучить некоторые сходства между Erlang и Java, чтобы попытаться предоставить отправную точку для изучения Erlang. Если я допустил какие-то вопиющие ошибки, то, надеюсь, кто-то с большим опытом работы с Эрлангом даст мне знать.
Когда вы начинаете, первое, что вам говорят об Erlang, это то, что это функциональный язык; однако, до того, как у вас возникнет апоплексия, это настолько хорошо структурированный функциональный язык, что вы могли бы подумать, что имеете дело с объектами.
Что я имею в виду под этим? В Java код хранится в файлах, которые представляют классы, а класс представляет собой группу данных и методы, которые выполняют единственную ответственность. Вы создаете экземпляр класса и обращаетесь к его методам через переменную или, вы можете обращаться к его статическим методам через имя класса.
В Erlang код хранится в файлах, называемых модулями, причем каждый модуль представляет собой группу функций, которые выполняют одну ответственность. Вы не можете создать экземпляр модуля, а переменные экземпляра и переменные класса не существуют; Вы можете использовать только переменные аргумента метода. Вы получаете доступ к методу через имя его модуля аналогично доступу к статическому методу Java. Как и классы Java, модули Erlang имеют частные и публичные функции.
Будучи разработчиком Java, я был рад обнаружить, что есть
Erlang плагин для затмения . Это потому, что быстрее выучить только язык, а не язык и совершенно новый набор инструментов разработки. Несколько консультантов Erlang, с которыми я разговаривал несколько месяцев назад, сказали, что они предпочитают использовать emacs, и я удивлялся, почему, пока не обнаружил, что плагин eclipse довольно ненадежный. Тем не менее, это достаточно хорошо, чтобы начать, и у него есть потенциал.
Есть и другие сходства, которые должны заставить любого разработчика Java чувствовать себя как дома: исходные файлы модуля Erlang компилируются в файлы .beam
которые затем запускаются на виртуальной машине Erlang; есть также eunit , эквивалент Erlang для JUnit , и log4erl , который, как следует из его названия, является версией Erlang Log4J. Существует автоматическая генерация документов с использованием edoc , версия Javadoc Эрланга и стандартная компоновка проекта, которая очень похожа на компоновку Maven, которая выглядит следующим образом:
Структура немного отличается от Maven: целевой каталог называется ebin, а каталоги src и test разделены на уровне каталога проекта, но за ним легко следовать, и вы к нему привыкли.
Из того, что я сказал до сих пор, можно подумать, что самое большое различие между Java и Erlang заключается в том, что файлы Java имеют расширение .java
а файлы Erlang имеют расширение .erl
. К сожалению, в этом есть нечто большее, во-первых, это маленький вопрос или странный синтаксис Эрланга 1 .
Чтобы исследовать это, я подумал, что я возьму свои существующие классы ShoppingCart
и ShoppingCartTest
и переведу их на Erlang. Эти два класса доступны в моем проекте telldontask
выглядят примерно так …
Класс ShoppingCart
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
public class ShoppingCart { private final List<Item> items; private PaymentMethod method; public ShoppingCart() { items = new ArrayList<Item>(); } public void addItem(Item item) { items.add(item); } public double calcTotalCost() { double total = 0.0 ; for (Item item : items) { total += item.getPrice(); } return total; } public double calcTotalCost( double shipping, double minShippingAmount) { double totalCost = calcTotalCost(); if (totalCost > minShippingAmount) { totalCost += shipping; } return totalCost; } public void setPaymentMethod(PaymentMethod method) { this .method = method; } public void pay( double shipping, double minShippingAmount) { double totalCost = calcTotalCost(shipping, minShippingAmount); method.pay(totalCost); } } |
ShoppingCartTest JUnit
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class ShoppingCartTest { /** * Test method for {@link tell_dont_ask.ask.ShoppingCart#getAllItems()}. */ @Test public void calculateTotalCost() { ShoppingCart instance = new ShoppingCart(); Item a = new Item( "gloves" , 23.43 ); instance.addItem(a); Item b = new Item( "hat" , 10.99 ); instance.addItem(b); Item c = new Item( "scarf" , 5.99 ); instance.addItem(c); double totalCost = instance.calcTotalCost(); assertEquals( 40.41 , totalCost, 0.0001 ); } } |
Приведенный выше код демонстрирует некоторые базовые функции корзины покупок; однако, для более подробной информации о том, как работают эти классы, взгляните на Определение Сказать Не спрашивать и Разборка Сказать Не спрашивать .
Эквивалентный код на Erlang выглядит примерно так:
Модуль shopping_cart
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
-module(shopping_cart). %% ==================================================================== %% API functions %% ==================================================================== -export([add_item/ 2 ,calc_total_cost/ 1 ,calc_total_cost/ 3 ,pay/ 3 ]). %% @doc Add an item to the order list add_item(OrderList,Item) -> [Item | OrderList]. %% @doc Calculate the total cost of all the items in a list. The List must have the following format: %% [{itemName, Price}] %% where %% itemName -> atom %% Price -> float () calc_total_cost(OrderList) -> round_dp(calc_total_cost( 0 ,OrderList)). %% @doc Calculate the total cost of all the items in a list adding a shipping cost if the value is below a certain limit. %% The Order List must have the following format: %% [{itemName, Price}] %% where %% itemName -> atom %% Price -> float () calc_total_cost(OrderList,Shipping, MinShippingAmount) -> Cost = calc_total_cost(OrderList), TotalCost = Cost + shipping(Cost,Shipping,MinShippingAmount), round_dp(TotalCost). %% @doc @todo Method not implemented pay(_Order,_Shipping, _MinShippingAmount) -> unimplemented. %% ==================================================================== %% Internal functions %% ==================================================================== calc_total_cost(Result,[{_,Price} | TheRest]) -> calc_total_cost(Result + Price,TheRest); calc_total_cost(Result,[]) -> Result. shipping(Cost,Shipping,MinShippingAmount) when Cost < MinShippingAmount -> Shipping; shipping(_,_,_) -> 0 . round_dp(Number) -> List = float_to_list(Number,[{decimals, 2 }]), list_to_float(List). |
Модуль shopping_cart_tests
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
-include_lib( "eunit/include/eunit.hrl" ). -module(shopping_cart_tests). %% ==================================================================== %% API functions %% ==================================================================== -export([]). %% @doc Calculate total code - written to match the Java style calculate_total_cost_test() -> EmptyList = [], OrderList1 = shopping_cart:add_item(EmptyList,{gloves, 23.43 }), OrderList2 = shopping_cart:add_item(OrderList1,{hat, 10.99 }), OrderList3 = shopping_cart:add_item(OrderList2,{scarf, 5.99 }), ?_assertEqual( 40.42 ,shopping_cart:calc_total_cost(OrderList3)). %% @doc Calculate total cost example - written in a better erlang style calculate_total_cost_2_test() -> OrderList = [{gloves, 23.43 },{hat, 10.99 },{scarf, 5.99 }], ?assertEqual( 40.41 ,shopping_cart:calc_total_cost(OrderList)). |
Эксперты говорят мне, что с Erlang вы можете сделать гораздо больше за меньшее количество строк кода. Это не тот случай, но с другой стороны, я добавил много строк комментариев (обозначается разделителем «%»).
По сравнению с Java приведенный выше код выглядит довольно странно (он также выглядит немного уродливо, так как у меня нет конвертера Erlang в HTML). Переменных экземпляра нет, поэтому любые необходимые данные передаются в качестве аргументов функции. Если вы посмотрите на add_item(…)
то увидите, что он добавляет переменную Item
в начало списка, например: items.add(item)
(обратите внимание, что в именах переменных Erlang всегда начинаются с заглавной буквы).
Переход к calc_total_cost()
и все начинает выглядеть очень странно… calc_total_cost(OrderList)
— это просто оболочка для calc_total_cost(0,OrderList)
. calc_total_cost(0,OrderList)
— это вызов либо calc_total_cost(Result,[{_,Price} | TheRest])
либо calc_total_cost(Result,[])
, которые являются функциями, которые выполняют циклическое добавление цен на товары из списка. , За исключением того, что он не зацикливается; В Erlang нет циклов for , вам нужно использовать рекурсию, постепенно добавляя цены в calc_total_cost(Result,[{_,Price} | TheRest])
а затем рекурсивно вызывать себя, пока список не станет пустым.
Суть синтаксиса Erlang в том, что, хотя разработчик Java привык к языкам, являющимся производными от C
, он очень логичен и поэтому его легко подобрать.
Стоит отметить, что вышеприведенный Erlang был написан для имитации Java. Вероятно, вы не подходите к разработке корзины покупок Erlang с нуля.
Почему вы выбрали бы Erlang вместо Java? Конечно, не за его сходство с Java. Вы бы выбрали Erlang, а не Java, когда его функции и преимущества помогут вам решить ваши проблемы более эффективно и с минимальными затратами. Согласно заявкам Ленарта Османа на Google Tech Talk Erlang, которые должны быть:
- отказоустойчивой
- без остановки
- параллельный
- распределенный, масштабируемый и разнородный
- мягкий в реальном времени
- требуют «прототипа»
Эти цели были достигнуты несколькими способами. Например, передача сообщений между процессами является частью языка, а не отдельным API. Чтобы отправить сообщение другому процессу, просто введите:
1
|
Pid ! theMessage, |
… Где Pid
— это идентификатор процесса, который получит theMessage
. Чтобы отправить сообщение другому процессу, запущенному на другой виртуальной машине Erlang, вы набираете:
1
|
Pid ! theMessage, |
Из этой несмешной шутки я мог догадаться, что процессы на виртуальных машинах Erlang прозрачны по расположению. Это означает, что процесс Erlang не отличается от того, на какой машине он работает; будь то локально или на другом физическом оборудовании. Это потому, что виртуальные машины Erlang могут общаться друг с другом и могут быть кластеризованы; что-то далеко за пределами JVM.
Чтобы получить сообщение, вы используете ключевое слово Erlang: receive
, что-то вроде этого:
1
2
3
4
5
6
|
%% @doc Receive a message and print the contents print() -> receive Message -> io:format( "The message is: ~p~n" ,[Message]) end. |
Сами процессы очень легковесны и предназначены для использования гиперпоточности на мегаядерных процессорах таким образом, что другие языки не могут имитировать. Процессы очень важны в Erlang, я где-то читал, что если Java является объектно-ориентированным языком, то Erlang является процессно-ориентированным языком.
Если этот блог читается как реклама для Erlang, то это потому, что после того, как я опустил голову в воду, я вижу, что есть ряд проблем, которые он может решить в моем текущем проекте дешевле и с меньшими хлопотами, чем решение на основе Java. , С другой стороны, существуют проблемы, для которых Java и Spring — обычная тема этого блога — лучше подходят. Компьютерные языки — это просто инструменты, и вы всегда должны выбирать лучший для этой работы.
1 Странно выглядит, если вы программист на Java, так как он основан на Прологе, а не на C.
Примеры кода Java для этого блога доступны в репозитории Captain Debug Github:
https://github.com/roghughe/captaindebug , хотя код Erlang доступен в моем репозитории Erlang Samples Github:
https://github.com/roghughe/erlang_samples .