如何使用灵丹妙药

Elixir是一门非常年轻的编程语言(于2011年诞生),但正逐渐普及。 我最初对这种语言很感兴趣,因为使用它时,您可以从不同的角度看程序员通常解决的一些常见任务。 例如,您可以找到如何在没有for循环的情况下遍历集合,或者如何在没有类的情况下组织代码。

Elixir具有一些非常有趣且强大的功能,如果您来自OOP领域,可能很难理解。 但是,经过一段时间,这一切都变得有意义了,您会看到功能代码的表达能力。 理解就是这样的功能之一,本文中我将解释如何使用它们。

理解与映射

一般而言, 列表理解是一种特殊的构造,它使您可以基于现有列表创建新列表。 这种概念可以在Haskell和Clojure等语言中找到。 Erlang也介绍了它,因此Elixir也具有理解力

您可能会问,理解力与map / 2函数有何不同? map / 2函数也需要一个集合并产生一个新集合? 那将是一个公平的问题! 好吧,在最简单的情况下,理解几乎是同一件事。 看一下这个例子:

defmodule MyModule do
  def do_something(list) do
    list |>
    Enum.map(fn(el) -> el * 2 end)
  end
end

MyModule.do_something([1,2,3]) |> IO.inspect # => [2,4,6]

在这里,我只是简单地获取一个包含三个数字的列表,并生成一个包含所有数字乘以2的新列表。 map调用可以进一步简化为Enum.map( &(&1 * 2) )

现在可以使用理解力来重写do_something/1函数:

def do_something(list) do
    for el <- list, do: el * 2
  end

这就是基本的理解,在我看来,代码比第一个示例更优雅。 在这里,我们再次从列表中获取每个元素,并将其乘以2el <- list部分称为generator ,它说明了您希望如何从集合中提取值。

请注意,我们并没有被迫将列表传递给do_something/1函数,该代码将可用于任何可枚举的东西:

defmodule MyModule do
  def do_something(collection) do
    for el <- collection, do: el * 2
  end
end

MyModule.do_something((1..3)) |> IO.inspect

在此示例中,我将范围作为参数传递。

理解也适用于二进制字符串。 语法略有不同,因为您需要用<<>>将生成器括起来。 让我们通过制作一个非常简单的函数来“解密”用Caesar cipher保护的字符串来演示这一点。 这个想法很简单:我们将单词中的每个字母替换为一个固定位置的字母。 为了简单起见,我将移动1位置:

defmodule MyModule do
  def decipher(cipher) do
    for << char <- cipher >>, do: char - 1
  end
end

MyModule.decipher("fmjyjs") |>
IO.inspect
# => 'elixir'

除了<<>>部分,这看起来与前面的示例几乎相同。 我们将字符串中每个字符的代码减一,然后构造一个字符串。 因此,加密后的消息是“ elixir”!

但是,还有更多。 理解的另一个有用功能是能够滤除某些元素。

理解与过滤

让我们进一步扩展最初的示例。 我将传递从120的整数范围,仅接受偶数元素,然后将它们乘以2

defmodule MyModule do
  require Integer
  def do_something(collection) do
    collection |>
    Stream.filter( &Integer.is_even/1 ) |>
    Enum.map( &(&1 * 2) )
  end
end

MyModule.do_something( (1..20) ) |> IO.inspect

在这里,我必须要求Integer模块能够使用is_even/1宏。 另外,我使用Stream来优化代码并防止迭代执行两次。

现在,让我们再次理解以下示例:

def do_something(collection) do
    for el <- collection, Integer.is_even(el), do: el * 2
  end

因此,如您所见, for可以接受一个可选的过滤器以跳过集合中的某些元素。

您不仅限于一个过滤器,因此以下代码也是合法的:

def do_something(collection) do
    for el <- collection, Integer.is_even(el), el < 10, do: el * 2
  end

所有偶数都将小于10 。 只是不要忘了用逗号分隔过滤器。

将针对集合的每个元素对过滤器进行评估,如果评估返回true ,则执行该块。 否则,将采用新元素。 有趣的是,生成器还可以通过使用when来过滤元素:

def do_something(collection) do
    for el when el < 10 <- collection, Integer.is_even(el), do: el * 2
  end

这与编写保护子句时的操作非常相似:

def do_something(x) when is_number(x) do
    # ...
end

对多个集合的理解

现在,假设我们一次没有一个,而是两个,我们想产生一个新的集合。 例如,从第一个集合中获取所有偶数,从第二个集合中获取奇数,然后将它们相乘:

defmodule MyModule do
  require Integer
  def do_something(collection1, collection2) do
    for el1 <- collection1, el2 <- collection2, Integer.is_even(el1), Integer.is_odd(el2),
    do: el1 * el2
  end
end

MyModule.do_something( (1..20), (5..10) ) |> IO.inspect

此示例说明了理解一次可以处理多个集合。 将采用collection1的第一个偶数并将其乘以collection2每个奇数。 接下来,将取并乘以collection1的第二个偶数整数,依此类推。 结果将是:

[10, 14, 18, 20, 28, 36, 30, 42, 54, 40, 56, 72, 50, 70, 90, 60, 84, 108, 70,
 98, 126, 80, 112, 144, 90, 126, 162, 100, 140, 180]

而且,结果值不必为整数。 例如,您可以返回一个元组,其中包含来自第一个和第二个集合的整数:

defmodule MyModule do
  require Integer
  def do_something(collection1, collection2) do
    for el1 <- collection1, el2 <- collection2, Integer.is_even(el1), Integer.is_odd(el2),
    do: {el1,el2}
  end
end

MyModule.do_something( (1..20), (5..10) ) |> IO.inspect
# => [{2, 5}, {2, 7}, {2, 9}, {4, 5}...]

对“进入”选项的理解

到目前为止,我们理解力的最终结果始终是列表。 实际上,这也不是强制性的。 您可以指定一个into参数,该参数接受一个包含结果值的集合。

该参数接受任何实现Collectable协议的结构,因此例如,我们可以生成如下图:

defmodule MyModule do
  require Integer
  def do_something(collection1, collection2) do
    for el1 <- collection1, el2 <- collection2,
    Integer.is_even(el1), Integer.is_odd(el2),
    into: Map.new,
    do: {el1,el2}
  end
end

MyModule.do_something( (1..20), (5..10) ) |> IO.inspect
# => %{2 => 9, 4 => 9, 6 => 9...}

在这里,我只是简单地说into: Map.new ,也可以替换into: %{} 。 通过返回{el1, el2}元组,我们基本上将第一个元素设置为键,将第二个元素设置为值。

但是,此示例并不是特别有用,因此让我们生成一个以数字为键,其平方为值的地图:

defmodule MyModule do
  def do_something(collection) do
    for el <- collection, into: Map.new,
    do: {el, :math.sqrt(el)}
  end
end

squares = MyModule.do_something( (1..20) ) |> IO.inspect
# => %{1 => 1.0, 2 => 1.4142135623730951, 3 => 1.7320508075688772,...}
squares[3] |> IO.puts
# => 1.7320508075688772

在此示例中,我直接使用Erlang的:math模块,因为毕竟所有模块的名称都是原子。 现在,您可以轻松找到120任何数字的平方。

理解和模式匹配

最后要提到的是,您还可以全面理解模式匹配。 在某些情况下,它可能非常方便。

假设我们有一张包含员工姓名和原始工资的地图:

%{"Joe" => 50, "Bill" => 40, "Alice" => 45, "Jim" => 30}

我想生成一个新地图,其中名称被小写并转换为原子,并且薪金是使用税率计算的:

defmodule MyModule do
  @tax 0.13
  def format_employee_data(collection) do
    for {name, salary} <- collection, into: Map.new,
    do: {format_name(name), salary - salary * @tax}
  end

  defp format_name(name) do
    name |>
    String.downcase |>
    String.to_atom
  end
end

MyModule.format_employee_data( %{"Joe" => 50, "Bill" => 40, "Alice" => 45, "Jim" => 30} ) |>
IO.inspect
# => %{alice: 39.15, bill: 34.8, jim: 26.1, joe: 43.5}

在此示例中,我们定义了具有任意数字的模块属性@tax 。 然后,我使用{name, salary} <- collection -collection解构理解中的数据。 最后,格式化名称并根据需要计算薪水,然后将结果存储在新地图中。 非常简单但富有表现力。

结论

在本文中,我们看到了如何使用Elixir理解。 您可能需要一些时间来习惯它们。 这种构造确实很整洁,在某些情况下可以比mapfilter这样的功能更好。 您可以在Elixir的官方文档入门指南中找到更多示例。

希望您发现本教程有用且有趣! 感谢您与我在一起,很快再见。

翻译自: https://code.tutsplus.com/tutorials/how-to-work-with-elixir-comprehensions--cms-28823

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值