Modules and Named Functions 学习摘要(3)

《Programming Elixir 1.3》p47-63

Posted by zhulinpinyu on January 9, 2017

Elixir 具名函数(Named Functions)必须定义在module中

编译module文件

源文件:times.exs

defmodule Times do
  def double(n) do
    n * 2
  end
end

编译方法一:

iex times.exs
iex(1)> Times.double(2)
#4

编译方法二:

$ iex
iex(1)> c "times.exs"
iex(2)> Times.double(2)
#4

知识点:

do...end在elixir中是用于组织代码块,不论是在module,function还是其他elixir code中,都是这样一个基本的用法。但是在编译时其被转为 do: CODE.也就是说do...end只是一个语法糖。 日常使用时:do: CODE语法用于单行代码,

def double(n), do: n * 2

do: CODE语法也可以用于多行代码,写法如下

def greet(greeting, name), do: (
	IO.puts greeting
	IO.puts "How're you doing, #{name}?"
)

日常书写多行代码更倾向于使用do...end 语法

def greet(greeting, name), do
	IO.puts greeting
	IO.puts "How're you doing, #{name}?"
end

练习:

defmodule Times do
  def double(n) do
    n * 2
  end

  def triple(n), do: n * 3

  def quadruple(n), do: double(n) * 2
end

运行结果:

$ iex times.exs
iex(2)> Times.double(2)
#4
iex(3)> Times.triple(2)
#6
iex(4)> Times.quadruple(2)
#8

函数调用和Pattern Matching

定义同名不同参数的函数,根据Pattern Matching,自动识别实际调用的函数。

注意: 同名且参数个数相同时函数的在module的定义顺序。如下示例就是不能正常运行的。

defmodule BadFactorial do
  def of(n), do: n * of(n-1)
  def of(0), do: 1
end

练习1:递归实现求和函数

源文件shuxue.exs

defmodule Shuxue do
  def sum(0), do: 0
  def sum(n), do: n + sum(n-1)
end

运行:

$ iex shuxue.exs
iex(1)> Shuxue.sum(1)
#1
iex(2)> Shuxue.sum(3)
#6
iex(3)> Shuxue.sum(100)
#5050
iex(4)> Shuxue.sum(0)
#0

练习2:求两个非负整数的最大公约数

源文件shuxue.exs

defmodule Shuxue do
  def gcd(x, 0), do: x
  def gcd(x, y), do: gcd(y, rem(x,y))
end

运行:

$ iex shuxue.exs
iex(1)> Shuxue.gcd(12,5)
#1
iex(2)> Shuxue.gcd(12,3)
#3
iex(3)> Shuxue.gcd(12,8)
#4

卫兵从句(Guard Clauses)

http://blog.zhulinpinyu.com/2016/12/19/elixir-funcitons/#section-4

示例:

defmodule Guard do
  def what_is(x) when is_number(x) do
    IO.puts "#{x} is a number"
  end
  def what_is(x) when is_list(x) do
    IO.puts "#{inspect(x)} is a list"
  end
  def what_is(x) when is_atom(x) do
    IO.puts "#{x} is an atom"
  end
end

Guard.what_is(99)        # => 99 is a number
Guard.what_is(:cat)      # => cat is an atom
Guard.what_is([1,2,3])   # => [1,2,3] is a list

个人点评:if不就可以搞定么,这不是弄复杂了么。虽说这使得语法更清晰。一家之言,欢迎拍砖。

实例:优化前文中的求和函数,使得不论module中函数的定义顺序如何皆可正常运行。

defmodule Shuxue do
  def sum(n) when n > 0, do: n + sum(n-1)
  def sum(n) when n === 0, do: 0
end

运行:

$ iex shuxue.exs
iex(1)> Shuxue.sum(0)
#0
iex(2)> Shuxue.sum(3)
#6

注意:卫兵从句不允许使用 ||或者&&

以下皆为可用之操作符或函数:

==, !=, ===, !==, >, <, <=, >=

or, and, not, !

+, -, *, /

<>, ++ #用于字符串或者集合join

#类型检查函数
is_atom is_binary is_bitstring is_boolean is_exception is_float is_function is_integer is_list is_map is_number is_pid is_port is_record is_reference is_tuple

#其他函数
abs(number) bit_size(bitstring) byte_size(bitstring) div(number,number) elem(tuple, n) float(term) hd(list) length(list) node() node(pid|ref|port) rem(number,number) round(number) self() tl(list) trunc(number) tuple_size(tuple)

函数默认参数

http://blog.zhulinpinyu.com/2016/12/19/elixir-funcitons/#section-3

练习: 二分法查找算法的实现

defmodule Chop do
  def guess(n, range = low..high) do
    mid = div(low+high, 2)
    IO.puts "Is it #{mid}?"
    _guess(n,mid,range)
  end

  defp _guess(n, n, _), do: IO.puts "it is #{n}"

  defp _guess(n, mid, low.._high)
    when mid > n,
    do: guess(n, low..guess-1)

  defp _guess(n, mid, _low..high)
    when mid < n,
    do: guess(n, guess+1..high)
end

前方高能: 这就是强大的Pattern Matching

defp _guess(n, n, _), do: IO.puts "it is #{n}"

私有函数

只能在定义的module中访问的函数,谓之私有函数,使用关键词defp定义。 注意: 多个同名函数不能既是private 又是public

牛逼闪闪的管道操作符|>

原始代码

def create_hand(hand_size) do
  deck = Cards.create_deck
  deck = Cards.shuffle(deck)
  Cards.deal(deck,hand_size)
end

使用管道操作符实现

def create_hand(hand_size) do
  Cards.create_deck
  |> Cards.shuffle
  |> Cards.deal(hand_size)
end

#或者
def create_hand(hand_size) do
  Cards.create_deck |> Cards.shuffle |> Cards.deal(hand_size)
end

上面的实践中,前面语句的输出作为下一个语句的输入,也就是说是下一个method的第一个参数.

注意:在管道操作符的使用中始终都要用圆括号包裹函数的参数

问题:当真正的输入参数是第二个时,怎样使用pipe管道操作符?

答: 这种情况无解,只能采用传统的办法做。

Modules

Modules首先是一个namespace。Ta有三个有用的directive,分别是import, alias,require


import 将其他moudle的函数/macro 导入到当前作用域中。

示例:导入Listflatten/1函数,这样使用flatten/1就不必书写List前缀

defmodule Example do
  def func1 do
     List.flatten [1,[2,3],4]
  end
  def func2 do
    import List, only: [flatten: 1]
    flatten [5,[6,7],8]
  end
end

import的语法:

import Module [, only:|except:]

比如导入List中所有的函数或者macro

import List

只导入Listflatten/1函数和duplicate/2函数

import List, only: [flatten: 1, duplicate: 2]

导入Listflatten/1duplicate/2以外的所有函数和macro

import List, except: [flatten: 1, duplicate: 2]

只导入module中的函数或者只导入module中的macro

import List, only: :functions

import List, only: :macros

alias

alias 自定义一个别名:

alias Application.User, as: Wanger
#自定义别名为Wanger

alias 一个(简写):

alias Discuss.Topic
#默认别名就是 Topic。实际的写法是:alias Discuss.Topic, as: Topic

alias 多个:

alias Discuss.{Topic, User}
#默认别名就是 Topic, User

Module 中Attributes

module中metadata被称之为module的attribute,语法为:@name value, 相同的属性可多次赋值:

defmodule Example do
  @attr "one"
  def first, do: @attr
  @attr "two"
  def second, do: @attr
end
IO.puts "#{Example.second} #{Example.first}"
#two one

Module背后的秘密

IO为例,实际上,以大写字母开头的Module名称,Elixir运行时都会将其转为atom.

iex(1)> is_atom IO
#true
iex(2)> to_string IO
#"Elixir.IO"
iex(3)> :"Elixir.IO" === IO
#true

iex(4)> :"Elixir.IO".puts 123
#123
iex(5)> IO.puts 123
#123

调用Erlang库函数

调用erlang的io module

iex(6)> :io.format("The number is ~3.1f~n", [5.678])
#The number is 5.7
:ok

练习:

#保留两位小数(使用erlang库)
iex> :io.format("~.2f~n", [2.0/3.0])
# 0.67

# Get the value of an operating system environment variable.
iex> System.get_env("HOME")
"/Users/dave"

#获取文件的扩展名称
iex> Path.extname("dave/test.exs")
".exs"

#获取当前工作目录
iex> System.cwd
"/Users/dave/BS2/titles/elixir/Book/yourturn/ModulesAndFunctions"

# Convert a string containing JSON into Elixir data structures
# There are many options. Some, such as https://github.com/guedes/exjson,
# are specifically for Elixir. Others, such as https://github.com/hio/erlang-json
# are Elnag libraries that are usable from Elixir.

#执行系统shell命令
iex> System.cmd("date")
#"Sun Jul 14 15:04:06 CDT 2013\n"