编写适用于BitStrings或Lists的库

时间:2015-06-11 17:16:21

标签: elixir

我正在开发一个涉及大量字节级操作的项目。我希望该库接受二进制文件作为输入,但使用Enum上的函数通常很方便。我最终得到了许多具有两个保护定义的函数,一个用于when is_binary,另一个用于when is_list

@doc """
Convert a binary or list of bytes to a hexadecimal string representation,

## Examples

    iex> Bytes.to_hex <<1,2,3,253,254,255>>
    "010203fdfeff"

    iex> Bytes.to_hex [1,2,3,253,254,255]
    '010203fdfeff'
"""
def to_hex(bytes) when is_binary(bytes) do
  :binary.bin_to_list(bytes) |> to_hex |> :binary.list_to_bin
end

def to_hex(bytes) when is_list(bytes) do
  hexes = Enum.map bytes, &byte_to_hex/1
  Enum.join(hexes) |> String.downcase |> :binary.bin_to_list
end

defp byte_to_hex(byte) do
  Integer.to_char_list(byte, 16) |> :string.right(2, ?0)
end

此外,列表版本末尾有:binary.bin_to_list,后面是字符串版本末尾的:binary.list_to_bin,导致多余的工作。

我真的希望能够对二进制文件使用相同的抽象。我最初尝试通过实现BitString的协议来实现这一点:

defimpl Enumerable, for: BitString do
  def count(coll) do
    {:ok, byte_size(coll)}
  end

  def member?(_coll, _val) do
    {:error, __MODULE__}
  end

  def reduce(_, {:halt, acc}, _fun) do
    {:halted, acc}
  end

  def reduce(bin, {:suspend, acc}, fun) do
    {:suspended, acc, &reduce(bin, &1, fun)}
  end

  def reduce(<<>>, {:cont, acc}, _fun) do
    {:done, acc}
  end

  def reduce(<< h :: binary-size(1), t :: binary >>, {:cont, acc}, fun) do
    reduce(t, fun.(h, acc), fun)
  end
end

这适用于countmemberreduce案例:

test "counts bytes" do
  assert Enum.count(<<0, 1, 2>>) == 3
end

test "reports members" do
  assert Enum.member?(<<0, 1, 2>>, <<3>>) == false
  assert Enum.member?(<<0, 1, 2>>, <<2>>) == true
end

test "reduces binaries" do
  reducer = fn(b, acc) -> b <> acc end
  assert Enum.reduce("abc", "", reducer) == "cba"
end

但是,某些Enum函数会调用:lists上的函数;例如,Enum.map调用:lists.reverse,因此这会产生不同的输出:

test "maps binaries" do
  mapper = fn(<<b>>) -> <<b + 1>> end
  assert Enum.map(<<0, 1, 2 >>, mapper) == <<1, 2, 3>>
end
1) test maps binaries
   Assertion with == failed
   code: Enum.map(<<0, 1, 2>>, mapper) == <<1, 2, 3>>
   lhs:  [<<1>>, <<2>>, <<3>>]
   rhs:  <<1, 2, 3>>

处理这种情况最常用的方法是什么?我应该简单地与警卫保持联系并实施清单&lt; - &gt;二进制转换?我应该为功能创建特定模块,例如ListEnum吗?

修改

我以为我可以处理额外的清单&lt; - &gt;通过使二进制文件成为基本案例进行二进制转换:

def to_hex(bytes) when is_binary(bytes) do
  bytes
  |> :binary.bin_to_list
  |> Enum.map(&byte_to_hex/1)
  |> Enum.join
end

def to_hex(bytes) when is_list(bytes) do
  bytes |> :binary.list_to_bin |> to_hex |> :binary.bin_to_list
end

defp byte_to_hex(byte) do
  Integer.to_string(byte, 16) |> String.rjust(2, ?0) |> String.downcase
end

但是,当然,此版本中仍有相同数量的list_to_binbin_to_list来电,只是四处移动。有没有办法解决这个问题?

1 个答案:

答案 0 :(得分:0)

看起来很奇怪你会得到二进制文件,而这些二进制文件只能枚举二进制文件。对我来说,他们应该是整数。就像一个char列表。

然后像Enum.map这样的函数会按预期工作。