Pull to refresh

Принудительное приведение типов в Erlang

Reading time6 min
Views2.8K
Принудительное приведение типов в Erlang

Erlang — во многом уникальный язык. Я начал его изучать недавно, и, хотя имею более десяти лет опыта в программировании, он продолжает меня удивлять своей гибкостью и удобством в различных моментах.
Но есть одно большое «НО», которое отсутствует в Erlang — это автоматическое приведение типов. Как человек, развращенный неявным приведением типов в большинстве современных языков программирования, я набросал модуль, который позволяет не задумываться о типе исходных данных.
В процессе написания собственной CMS на базе Erlang я постоянно пользуюсь им и рекомендую его для других программистов на Erlang.

Откуда и куда?


Проблема приведения типов состоит из двух частей — определение исходного типа и перевод в требуемый. В Erlang для обоих процедур существуют встроенные средства, но, к сожалению, они не удобны для частого использования.

Определение типа


Для выяснения типа исходных данных существует набор функций из модуля erlang. Все они начинаются с префикса is_. Что приятно в этих функциях — их можно использовать в качестве стражей (guards). Для неофитов: guards — это встроенная в erlang проверка данных перед началом выполнения функции.
В качестве лобовой атаки можно было использовать подобный метод для прямого преобразования:

to_list(Item) when is_atom(Item)-> ...;
to_list(Item) when is_binary(Item)-> ...;
...


Приведение типа


Большинство типов можно легко перевести в другие с использованием встроенных функций из того же модуля. Казалось бы, вот и нет проблем, сравнивай и приводи тип к нужному.
Но для обработки всех встроенных типов пришлось бы делать слишком большое количество сравнений, по крайней мере большее, чем мне позволяет лень.
Поэтому я разбил всю процедуру на несколько шагов, воспользовавшись еще одной функцией — apply.
Эта функция с арностью 3 (арность — термин Erlang, число передаваемых в функцию параметров) получает атом с именем модуля, атом с именем функции, массив данных и возвращает результат работы указанной функции. В документации указана еще одна арность для этой функции — 2, но в таком случае возможны конфликты между модулями.

Что получилось



Первая функция — force, определяет исходный тип данных и вызывает вторую функцию — pass. Вторая функция собирает вызов системной функции приведения типа из оригинального и требуемого типов и возвращает приведенное значение.
Попутно добавлены функции-сокращения для более легкого использования модуля.

К сожалению, не для всех типов оказалось возможным такое прямое приведение. Поэтому в случаях, когда прямое приведение невозможно, данные сначала приводятся к типу list, который является универсальным промежуточным типом, а потом уже к требуемому типу.

Использование этого модуля примитивно до невероятности:
force:to_list('this_is_a_long_atom')


Код легко читаем и самодокументирован.

% =====================================================================================
% CMSaaS - Content Management System as a Service
%
% @copyright 2011 CMSaaS dev team
% @version 0.1
% All rights reserved.
%
% CC-Attribution License
% 
% Redistribution and use in source and binary forms, with or without modification, are permitted provided
% that the following conditions are met:
%
%  * Redistributions of source code must retain the above copyright notice, this list of conditions and the
%	 following disclaimer.
%  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
%	 the following disclaimer in the documentation and/or other materials provided with the distribution.
%  * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
%	 products derived from this software without specific prior written permission.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
% POSSIBILITY OF SUCH DAMAGE.
% =====================================================================================

-module(force).

-export([get_type/1, to_list/1, to_list/2, to_atom/1, to_atom/2, to_binary/1, to_binary/2, to_integer/1, to_integer/2, to_bitstring/1, to_bitstring/2, to_float/1, to_float/2, to_pid/1, to_pid/2, to_tuple/1, to_tuple/2]).

force(Item, New_type) ->
	force(Item, New_type, [])
.

force(Item, New_type, Options) when is_list(Item)-> pass(Item, 'list', New_type, Options);
force(Item, New_type, Options) when is_atom(Item)-> 
	case New_type of
		'list' -> 
			pass(Item, 'atom', New_type, Options)
		;
		'binary' -> 
			New_option=case Options of 
				[] -> [utf8];
				X -> X
			end,
			pass(Item, 'atom', New_type, New_option)
		;
		_ ->
			pass(pass(Item, 'atom', 'list', []), 'list', New_type, Options)
	end
;
force(Item, New_type, Options) when is_binary(Item)-> 
	case New_type of
		'atom' ->
			New_option=case Options of 
				[] -> [utf8];
				X -> X
			end,
			pass(Item, 'binary', New_type, New_option)
		;
		'list' ->
			pass(Item, 'binary', New_type, Options)
		;
		_ ->
			pass(pass(Item, 'binary', 'list', Options), 'list', New_type, [])
	end
;
force(Item, New_type, Options) when is_bitstring(Item)-> 
	case New_type of
		'list' ->
			pass(Item, 'bitstring', New_type, Options)
		;
		_ ->
			pass(pass(Item, 'bitstring', 'list', []), 'list', New_type, Options)
	end
;
force(Item, New_type, Options) when is_boolean(Item)-> 
	case New_type of
		'integer' -> 
			pass(Item, 'boolean', New_type, Options)
		;
		'float' ->
			pass(Item, 'boolean', New_type, Options)
		;
		_ ->
			pass(Item, 'atom', New_type, Options)
	end
;
force(Item, New_type, Options) when is_float(Item)-> 
	case New_type of
		'list' ->
			pass(Item, 'float', New_type, Options)
		;
		_ ->
			pass(pass(Item, 'float', 'list', []), 'list', New_type, Options)
	end
;
force(Item, New_type, Options) when is_integer(Item)-> 
	case New_type of
		'list' ->
			pass(Item, 'integer', New_type, Options)
		;
		_ ->
			pass(pass(Item, 'integer', 'list', []), 'list', New_type, [])
	end
;
force(Item, New_type, Options) when is_pid(Item)-> 
	pass(pass(Item, 'pid', 'list', []), 'list', New_type, Options)
;
force(Item, New_type, Options) when is_port(Item)-> 
	pass(pass(Item, 'port', 'list', []), 'list', New_type, Options)
;
force(Item, New_type, Options) when is_reference(Item)-> 
	pass(pass(Item, 'ref', 'list', []), 'list', New_type, Options)
;
force(Item, New_type, Options) when is_tuple(Item)-> 
	pass(pass(Item, 'tuple', 'list', []), 'list', New_type, Options)
;
force(_Item, New_type, Options) -> 
	pass(pass(undefined, 'atom', 'list', []), 'list', New_type, Options)
.

pass(Item, Old_type, New_type, _Options)  when Old_type==New_type-> Item;
pass(Item, Old_type, New_type, Options) ->
	apply(erlang, list_to_atom(atom_to_list(Old_type)++"_to_"++atom_to_list(New_type)), lists:merge([Item], Options))
.


get_type(Item) when is_list(Item)-> 'list';
get_type(Item) when is_atom(Item)-> 'atom';
get_type(Item) when is_binary(Item)-> 'binary';
get_type(Item) when is_bitstring(Item)-> 'bitstring';
get_type(Item) when is_boolean(Item)-> 'atom';
get_type(Item) when is_float(Item)-> 'float';
get_type(Item) when is_integer(Item)-> 'integer';
get_type(Item) when is_pid(Item)-> 'pid';
get_type(Item) when is_port(Item)-> 'port';
get_type(Item) when is_reference(Item)-> 'ref';
get_type(Item) when is_tuple(Item)-> 'tuple';
get_type(_Item) -> undefined.


to_list(Item) when is_list(Item) -> Item;
to_list(Item)  -> force(Item, 'list').

to_list(Item, _Options) when is_list(Item) -> Item;
to_list(Item, Options) -> force(Item, 'list', Options).

to_atom(Item) when is_atom(Item) -> Item;
to_atom(Item) -> force(Item, 'atom').

to_atom(Item, _Options)  when is_atom(Item) -> Item;
to_atom(Item, Options) -> force(Item, 'atom', Options).

to_binary(Item) when is_binary(Item) -> Item;
to_binary(Item) -> force(Item, 'binary').

to_binary(Item, _Options) when is_binary(Item) -> Item;
to_binary(Item, Options) -> force(Item, 'binary', Options).

to_integer(Item) when is_integer(Item) -> Item;
to_integer(Item) -> force(Item, 'integer').

to_integer(Item, _Options) when is_integer(Item) -> Item;
to_integer(Item, Options) -> force(Item, 'integer', Options).

to_bitstring(Item) when is_bitstring(Item) -> Item;
to_bitstring(Item) -> force(Item, 'bitstring').

to_bitstring(Item, _Options) when is_bitstring(Item) -> Item;
to_bitstring(Item, Options) -> force(Item, 'bitstring', Options).

to_float(Item) when is_float(Item) -> Item;
to_float(Item) -> force(Item, 'float').

to_float(Item, _Options) when is_float(Item) -> Item;
to_float(Item, Options) -> force(Item, 'float', Options).

to_pid(Item) when is_pid(Item) -> Item;
to_pid(Item) -> force(Item, 'pid').

to_pid(Item, _Options)  when is_pid(Item)-> Item;
to_pid(Item, Options) -> force(Item, 'pid', Options).

to_tuple(Item)  when is_tuple(Item)-> Item;
to_tuple(Item) -> force(Item, 'tuple').

to_tuple(Item, _Options)  when is_tuple(Item)-> Item;
to_tuple(Item, Options) -> force(Item, 'tuple', Options).

Tags:
Hubs:
+3
Comments13

Articles