1. 函数定义

R

In [2]:
hypot1 = function(x,y){
    x = abs(x)
    y = abs(y)
    if(x > y) {
        r = y/x
        return(x*sqrt(1+r^2))
    }
    if(y == 0) {
        return(zero(x))
    }
    r = x/y
    return(y*sqrt(1+r^2))
}
hypot1(3,4)
5

Python

In [2]:
import numpy as np

def hypot1(x,y):
    x = abs(x)
    y = abs(y)
    if x>y:
        r = y/x
        return x*np.sqrt(1+r**2)
    if y==0:
        return zero(x)
    r = x/y
    return y*np.sqrt(1+r**2)

hypot1(3,4)
Out[2]:
5.0

Julia

In [1]:
function hypot1(x,y)#::Int16
    x = abs(x)
    y = abs(y)
    if x > y
        r = y/x
        return x*sqrt(1+r*r)
    end
    if y==0
        return zero(x)
    end
    r = x/y
    return y*sqrt(1+r*r)
end

hypot1(3,4)
Out[1]:
5.0

2. 具名元组(tuple)

Julia

In [2]:
x = (a=1, b=1+1)
x.a
Out[2]:
1

Python

Python元组没有此功能

In [1]:
x = (a=1, b=1+1)
  File "<ipython-input-1-e178b33b49e1>", line 1
    x = (a=1, b=1+1)
          ^
SyntaxError: invalid syntax

R

R中没有tuple这种数据结构,( )c相结合c( )用来定义最普通的数组

In [1]:
c(1,2,3)
  1. 1
  2. 2
  3. 3

3. 参数解构

如果一个函数的参数被写成了元组形式 (如 (x, y)) 而不是简单的符号,那么一个赋值运算 (x, y) = argument 将会被默认插入:

Julia

In [1]:
minmax(x, y) = (y < x) ? (y, x) : (x, y)
range((min, max)) = max - min
range(minmax(10, 2))
Out[1]:
8

4. 变参函数 (Varargs Functions)

Julia

通过在最后一个参数后面增加一个省略号来定义一个变参函数:

In [2]:
bar(a,b,x...) = (a, b, x)
Out[2]:
bar (generic function with 1 method)

变量 a 和 b 和以前一样被绑定给前两个参数,后面的参数整个做为迭代集合被绑定到变量 x 上, 在所有这些情况下,x 被绑定到传递给 bar 的尾随值的元组:

In [3]:
bar(1,2)
Out[3]:
(1, 2, ())
In [4]:
bar(1,2,3)
Out[4]:
(1, 2, (3,))
In [5]:
bar(1,2,3,4,5)
Out[5]:
(1, 2, (3, 4, 5))

另一方面,将可迭代集中包含的值拆解为单独的参数进行函数调用通常很方便。 要实现这一点,需要在函数调用中额外使用 ... 而不仅仅只是变量:

In [8]:
x = (3,4)
bar(1,2,x...)
Out[8]:
(1, 2, (3, 4))
In [9]:
bar(1,2,x)
Out[9]:
(1, 2, ((3, 4),))

在这个情况下一组值会被精确切片成一个可变参数调用,这里参数的数量是可变的。

In [11]:
x = (2,3,4)
bar(1,x...)
Out[11]:
(1, 2, (3, 4))
In [12]:
x = (1,2,3,4)
bar(x...)
Out[12]:
(1, 2, (3, 4))

进一步,拆解给函数调用中的可迭代对象不需要是个元组:

In [13]:
x = [1,2,3,4]
bar(x...)
Out[13]:
(1, 2, (3, 4))

另外,参数可拆解的函数也不一定就是变参函数 —— 尽管一般都是:

In [14]:
baz(a,b) = a + b
Out[14]:
baz (generic function with 1 method)
In [16]:
args = [1,2]
baz(args...)
Out[16]:
3

如果要拆解的容器(比如元组或数组)元素数量不匹配就会报错,和直接给多个参数报错一样

In [17]:
args = [1,2,3]
baz(args...)
MethodError: no method matching baz(::Int64, ::Int64, ::Int64)
Closest candidates are:
  baz(::Any, ::Any) at In[14]:1

Stacktrace:
 [1] top-level scope at In[17]:2

5. 关键字参数

Julia

某些函数需要大量参数,或者具有大量行为。记住如何调用这样的函数可能很困难。关键字参数允许通过名称而不是仅通过位置来识别参数,使得这些复杂接口易于使用和扩展。

请注意,这样做有两个目的。调用更可读,因为我们能以其意义标记参数。也使得大量参数的任意子集都能以任意次序传递。另外,关键字参数必须指定默认值

具有关键字参数的函数在签名中使用分号定义:

In [19]:
function plot(x, y; style="solid", width=1, color="black")
    ###
end
Out[19]:
plot (generic function with 1 method)

关键字参数的类型可以通过如下的方式显式指定:

In [ ]:
function f(;x::Int=1)
    ###
end

附加的关键字参数可用 ... 收集,正如在变参函数中:

In [ ]:
function f(x; y=0, kwargs...)
    ###
end

如果一个关键字参数在方法定义中未指定默认值,那么它就是必须的:如果调用者没有为其赋值,那么将会抛出一个 UndefKeywordError 异常:

In [ ]:
function f(x; y)
    ###
end
f(3, y=5) # ok, y is assigned
f(3)      # throws UndefKeywordError(:y)

在 f 内部,kwargs 会是一个具名元组。具名元组(以及字典)可作为关键字参数传递,通过在调用中使用分号,例如 f(x, z=1; kwargs...)。

在分号后也可传递 key => value 表达式。例如,plot(x, y; :width => 2) 等价于 plot(x, y, width=2)。当关键字名称需要在运行时被计算时,这就很实用了。

Python

Python的函数定义中没有;以示区别

In [6]:
def f(x,y,alpha = 0.05):
    return alpha*(x**2+y)

f(2,3, alpha = 0.02)
Out[6]:
0.14

R

R的函数定义中也没有;以示区别

另外,R同Python一个区别就是,关键字参数在之后调用时其位置没有Python那么严格,需要一一对应,可以任意放

In [45]:
f <- function(x, y, alpha = 0.05) {
    alpha*(x^2+y)
}

f(alpha = 0.02,2,3)
0.14

6. 默认值作用域的计算

Julia

当计算可选和关键字参数的默认值表达式时,只有先前的参数才在作用域内。例如,给出以下定义:

In [20]:
# a=b 中的 b 指的是外部作用域内的 b,而不是后续参数中的 b。
function f(x, a=b, b=1)
    x+a+b
end

f(2)
UndefVarError: b not defined

Stacktrace:
 [1] f(::Int64) at ./In[20]:2
 [2] top-level scope at In[20]:4

Python

In [1]:
def f(x, a = b, b=1):
    x+a+b
    
f(2)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-7b9f1ef55ea1> in <module>()
----> 1 def f(x, a = b, b=1):
      2     x+a+b
      3 
      4 f(2)

NameError: name 'b' is not defined

R

但R的作用域是基于Lexical Scoping,当a没有直接定义而b定义时,它会根据a=b将b的值域给a

In [1]:
f = function(x, a=b, b=1) {
    x+a+b
}
f(2)
4

7. 函数参数

Julia

把函数作为参数传递给其他函数是一种强大的技术,比如三种语言都有的map/Map函数,R语言中的apply系列函数,当函数参数占据多行时,直接通过匿名函数调用便特别难以编写,例如调用map函数:

In [1]:
map(x -> begin
             if x < 0 && iseven(x)
                 return 0
             elseif x == 0
                 return 1
             else
                 return x
             end
           end,
       [-2,0,2]) 
Out[1]:
3-element Array{Int64,1}:
 0
 1
 2

使用保留字do重写此代码:

In [2]:
map([-2,0,2]) do x
    if x < 0 && iseven(x)
        return 0
    elseif x == 0
        return 1
    else
        return x
    end
end
Out[2]:
3-element Array{Int64,1}:
 0
 1
 2

R

In [38]:
lapply(c(-2,0,2),function(x) {
    if(x<0 && (x%%2 == 0)) {
        return(0)
    }
    if(x == 0) {
        return(1)
    } else { 
        return(x)
    }
})
  1. 0
  2. 1
  3. 2
In [39]:
Map(function(x) {
    if(x<0 && (x%%2 == 0)) {
        return(0)
    }
    if(x == 0) {
        return(1)
    } else { 
        return(x)
    }
}, c(-2,0,2))
  1. 0
  2. 1
  3. 2