telecomunicaciones - Erlang: la variable está desatada
erlang tutorial (3)
Las reglas para la coincidencia de patrones son que si una variable X ocurre en dos subpatrones, como en {X, X}, o {X, [X]}, o similar, entonces tienen que tener el mismo valor en ambas posiciones, pero el La coincidencia de cada subpatrón se sigue haciendo en el mismo entorno de entrada: las vinculaciones de un lado no se transfieren al otro. La verificación de igualdad se realiza conceptualmente después, como si hubiera coincidido en {X, X2} y agregó un guardia X =: = X2. Esto significa que su campo Longitud en la tupla no puede usarse como entrada para el patrón binario, ni siquiera si lo convierte en el elemento más a la izquierda.
Sin embargo, dentro de un patrón binario, las variables vinculadas en un campo se pueden usar en otros campos siguientes, de izquierda a derecha. Por lo tanto, los siguientes trabajos (utilizando un campo de 32 bits de tamaño en el binario):
1> <<Length:32, A:Length/binary, Rest/binary>> = <<0,0,0,3,1,2,3,4,5>>.
<<0,0,0,3,1,2,3,4,5>>
2> A.
<<1,2,3>>
3> Rest.
<<4,5>>
¿Por qué la siguiente variable diciendo es independiente?
9> {<<A:Length/binary, Rest/binary>>, Length} = {<<1,2,3,4,5>>, 3}.
* 1: variable ''Length'' is unbound
Está bastante claro que Length
debería ser 3.
Estoy tratando de tener una función con coincidencia de patrones similares, es decir .:
parse(<<Body:Length/binary, Rest/binary>>, Length) ->
Pero si falla con la misma razón. ¿Cómo puedo lograr la coincidencia de patrones que quiero?
Lo que realmente estoy tratando de lograr es analizar en paquetes de transmisión de tcp entrantes como LTV (longitud, tipo, valor).
En algún momento después de analizar el Longitud y el Tipo, quiero preparar solo hasta Length
número de bytes como el valor, ya que el resto será probablemente para el siguiente LTV.
Entonces mi función parse_value
es así:
parse_value(Value0, Left, Callback = {Module, Function},
{length, Length, type, Type, value, Value1}) when byte_size(Value0) >= Left ->
<<Value2:Left/binary, Rest/binary>> = Value0,
Module:Function({length, Length, type, Type, value, lists:reverse([Value2 | Value1])}),
if
Rest =:= <<>> ->
{?MODULE, parse, {}};
true ->
parse(Rest, Callback, {})
end;
parse_value(Value0, Left, _, {length, Length, type, Type, value, Value1}) ->
{?MODULE, parse_value, Left - byte_size(Value0), {length, Length, type, Type, value, [Value0 | Value1]}}.
Si pudiera hacer la coincidencia de patrones, podría dividirla en algo más agradable a la vista.
Me he encontrado con esto antes. Existe cierta rareza entre lo que sucede dentro de la sintaxis binaria y lo que sucede durante la unificación (coincidencia). Sospecho que es solo que la sintaxis binaria y la coincidencia se producen en diferentes momentos en la máquina virtual en algún lugar (no sabemos qué Length
no se puede asignar; tal vez la coincidencia binaria siempre sea la primera en evaluar, por lo que la Length
sigue sin tener sentido). Una vez iba a investigar y descubrir, pero luego me di cuenta de que nunca realmente necesité resolver este problema , que podría ser el motivo por el cual nunca fue "resuelto".
Afortunadamente, esto no te detendrá con lo que sea que estés haciendo.
Desafortunadamente, no podemos ayudar más a menos que explique el contexto en el que cree que este tipo de coincidencia es una buena idea (está teniendo un problema XY).
En el análisis binario, siempre puede forzar la situación para que sea una de las siguientes:
- Tenga un encabezado de tamaño fijo al comienzo del mensaje binario que le indique el siguiente elemento de tamaño que necesita (y de allí puede continuar como una cadena de asociaciones sin fin)
- Inspeccione el binario una vez en la entrada para determinar el tamaño que está buscando, extraiga ese valor y luego comience la tarea de análisis real
- Tener un conjunto de campos, todos los tamaños predeterminados que se ajustan a un estándar de esquema binario
- Convierta el binario a una lista y repítalo con cualquier cantidad arbitraria de anticipación y retroceso que pueda necesitar
Solución rápida
Sin saber nada más sobre su problema general, una solución típica se vería así:
parse(Length, Bin) ->
<<Body:Length/binary, Rest/binary>> = Bin,
ok = do_something(Body),
do_other_stuff(Rest).
Pero huelo algo funky aquí.
Tener cosas como esta en su código es casi siempre una señal de que un aspecto más fundamental de la estructura del código no está de acuerdo con los datos que está manejando.
Pero los plazos
Erlang se trata de un código práctico que satisface tus objetivos en el mundo real. Con esto en mente, le sugiero que haga algo como lo anterior por el momento, y luego regrese a este dominio del problema y vuelva a pensarlo. Luego refactorízala. Esto te reportará tres beneficios:
- Algo funcionará de inmediato.
- Más tarde aprenderá algo fundamental sobre el análisis en general.
- Es casi seguro que tu código se ejecutará más rápido si se ajusta mejor a tus datos.
Ejemplo
Aquí hay un ejemplo en el shell:
1> Parse =
1> fun
1> (Length, Bin) when Length =< byte_size(Bin) ->
1> <<Body:Length/binary, Rest/binary>> = Bin,
1> ok = io:format("Chopped off ~p bytes: ~p~n", [Length, Body]),
1> Rest;
1> (Length, Bin) ->
1> ok = io:format("Binary shorter than ~p~n", [Length]),
1> Bin
1> end.
#Fun<erl_eval.12.87737649>
2> Parse(3, <<1,2,3,4,5>>).
Chopped off 3 bytes: <<1,2,3>>
<<4,5>>
3> Parse(8, <<1,2,3,4,5>>).
Binary shorter than 8
<<1,2,3,4,5>>
Tenga en cuenta que esta versión es un poco más segura, ya que evitamos un bloqueo en el caso de que Length
sea más larga que el binario. Esta es otra buena razón por la que tal vez no podamos hacer eso en la cabeza de la función.
Pruebe con el siguiente código:
{<<A:Length/binary, Rest/binary>>, _} = {_, Length} = {<<1,2,3,4,5>>, 3}.