딥스탯 2018. 9. 27. 13:47
6_Functions(한글)

Functions

출처

https://github.com/JuliaComputing/JuliaBoxTutorials/tree/master/introductory-tutorials/intro-to-julia (github : JuliaComputing/JuliaBoxTutorials/introductory-tutorials/intro-to-julia/)

Topics:

  1. 함수 선언
  2. 줄리아에서 Duck-typing
  3. Mutating 과 non-mutating 함수
  4. 몇 가지 higher order functions
  5. 연습문제

함께보기

함수 선언

줄리아에는 함수를 선언하는 몇 가지 방법이 있다.

첫번째는 function, end 문을 이용하는 방법이다.

In [1]:
function 안녕(이름)
    println("안녕 $이름, 만나서 반가워!")
end
Out[1]:
안녕 (generic function with 1 method)
In [2]:
function f()
    ^2
end
Out[2]:
f (generic function with 1 method)

이 함수를 사용할 때는 아래와 같이 한다.

In [3]:
안녕("C-3PO")
안녕 C-3PO, 만나서 반가워!
In [4]:
f(42)
Out[4]:
1764

이 함수들을 한 줄로 쓰는 방법도 있다.

In [5]:
안녕2(이름) = println("안녕 $이름, 만나서 반가워!")
Out[5]:
안녕2 (generic function with 1 method)
In [6]:
f2() = ^2
Out[6]:
f2 (generic function with 1 method)
In [7]:
안녕2("R2D2")
안녕 R2D2, 만나서 반가워!
In [8]:
f2(42)
Out[8]:
1764

마지막으로 "anonymous" 함수로 쓰는 방법이 있다.

In [9]:
안녕3 = 이름 -> println("안녕 $이름, 만나서 반가워!")
Out[9]:
#3 (generic function with 1 method)
In [10]:
f3 =  -> ^2
Out[10]:
#5 (generic function with 1 method)
In [11]:
안녕3("츄바카")
안녕 츄바카, 만나서 반가워!
In [12]:
f3(42)
Out[12]:
1764

줄리아에서 Duck-typing

"오리처럼 꽥꽥대면, 오리다."

줄리아 함수는 input으로 동작할법한 형태로 넣어주기만 하면 작동한다.

예를 들어서 안녕 함수에 input으로 character 대신에 integer 타입을 넣는다면,

In [13]:
안녕(55595472)
안녕 55595472, 만나서 반가워!

함수 f도 input으로 matrix를 넣을 수 있다.

In [14]:
A = rand(3, 3)
Out[14]:
3×3 Array{Float64,2}:
 0.740961  0.369792  0.597127 
 0.699034  0.741633  0.492968 
 0.417486  0.989789  0.0903989
In [15]:
f(A)
Out[15]:
3×3 Array{Float64,2}:
 1.05681  1.13928   0.678724
 1.24219  1.29645   0.827577
 1.03898  0.977919  0.745399

f 는 string 을 input으로 넣어도 작동한다. 이유는 * operator가 문자를 합치는 역할을 하기 때문이다. 예를 들어, "안녕"을 넣으면,

In [16]:
f("안녕")
Out[16]:
"안녕안녕"

반면에, f는 벡터에는 오류나는데, 정방행렬과는 다르게 벡터의 제곱은 정의되지 않았기 때문이다.

In [17]:
v = rand(3)
Out[17]:
3-element Array{Float64,1}:
 0.42053378869891533
 0.83185728430284   
 0.8853302310561109 
In [18]:
f(v)
MethodError: no method matching ^(::Array{Float64,1}, ::Int64)
Closest candidates are:
  ^(!Matched::Float16, ::Integer) at math.jl:782
  ^(!Matched::Missing, ::Integer) at missing.jl:120
  ^(!Matched::Missing, ::Number) at missing.jl:93
  ...

Stacktrace:
 [1] macro expansion at ./none:0 [inlined]
 [2] literal_pow at ./none:0 [inlined]
 [3] f(::Array{Float64,1}) at ./In[2]:2
 [4] top-level scope at In[18]:1

Mutating 과 non-mutating 함수

규칙에 의해서, (무조건 적용되는 문법은 아님) 함수에 ! 가 붙으면 input object를 변경하고, ! 가 안 붙으면 input object를 변경하지 않는다.

아래에서 sortsort! 의 차이를 보자.

In [19]:
v = [3, 5, 2]
Out[19]:
3-element Array{Int64,1}:
 3
 5
 2
In [20]:
sort(v)
Out[20]:
3-element Array{Int64,1}:
 2
 3
 5
In [21]:
v
Out[21]:
3-element Array{Int64,1}:
 3
 5
 2

sort(v) 는 v 를 정렬했지만, v 자체를 바꾸지는 않는다.

반면, sort!(v) 를 실행하면 v 또한 바뀐다.

In [22]:
sort!(v)
Out[22]:
3-element Array{Int64,1}:
 2
 3
 5
In [23]:
v
Out[23]:
3-element Array{Int64,1}:
 2
 3
 5
In [24]:
 = 4
println(f())

16
Out[24]:
4
In [25]:
 = 4
println(f!())

UndefVarError: f! not defined

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

몇 가지 Higher order functions

Higher-order-function 이란, 아래 두 가지 중 하나 이상을 만족하는 함수다.

  • 함수를 파라미터로 전달받는 함수
  • 함수를 리턴하는 함수

map

map 은 input 중 하나를 함수로 받는 "higher-order" 함수다. map 은 함수를 받아서 data structure에 있는 모든 element에 적용시킨다. 예를 들어서,

map(f, [1, 2, 3])

는 모든 elements [1, 2, 3]에 함수 f 를 적용시킨 array를 출력한다.

[f(1), f(2), f(3)]
In [26]:
map(f, [1, 2, 3])
Out[26]:
3-element Array{Int64,1}:
 1
 4
 9

vector [1, 2, 3]을 제곱한게 아니고, [1, 2, 3] elements 들을 제곱한 결과가 나온다.

아래는 named 함수 대신에 anonymous 함수를 써서 하는 코드다.

In [27]:
 -> ^3
Out[27]:
#7 (generic function with 1 method)

via

In [28]:
map( -> ^3, [1, 2, 3])
Out[28]:
3-element Array{Int64,1}:
  1
  8
 27

[1, 2, 3] 의 모든 element를 세제곱했다!

broadcast

broadcast는 다른 higher-order 함수다. broadcastmap의 일반화된 함수라서 map이 할 수 있는 것은 다 할 수 있고, 거기에 더해서 다른 것도 더 할 수 있다. broadcast를 사용하는 구문은 map을 사용하는 것과 같다.

In [29]:
broadcast(f, [1, 2, 3])
Out[29]:
3-element Array{Int64,1}:
 1
 4
 9

broadcasting을 이용해서 [1, 2, 3]의 모든 원소에 함수 f를 적용했다.

broadcasting을 사용하는 다른 방법은, 함수명 옆에 . 을 찍고 input 을 적는 것이다. 예를 들어,

broadcast(f, [1, 2, 3])

는 아래와 같다.

f.([1, 2, 3])
In [30]:
f.([1, 2, 3])
Out[30]:
3-element Array{Int64,1}:
 1
 4
 9

이게

f([1, 2, 3])

랑 어떻게 다른지 다시 한 번 설명하자면, 우리는 vector를 제곱할 수는 없지만, vector의 element들을 제곱할 수는 있다.

f(A)f.(A) 의 연산이 어떻게 다른지 보기 위해서 아래와 같은 matrix A를 뒀다.

In [31]:
A = [i + 3*j for j in 0:2, i in 1:3]
Out[31]:
3×3 Array{Int64,2}:
 1  2  3
 4  5  6
 7  8  9
In [32]:
f(A)
Out[32]:
3×3 Array{Int64,2}:
  30   36   42
  66   81   96
 102  126  150
f(A) = A^2 = A * A

이다. 반면에,

In [33]:
B = f.(A)
Out[33]:
3×3 Array{Int64,2}:
  1   4   9
 16  25  36
 49  64  81

는 A의 element들을 제곱했다.

broadcasting을 위한 점 구문은 비교적 복잡한 elementwise 구문을 더 수학적 표현방법에 더 가깝거나 자연스러워 보이도록 한다. 예를 들어,

In [34]:
broadcast( ->  + 2 * f() / , A)
Out[34]:
3×3 Array{Float64,2}:
  3.0   6.0   9.0
 12.0  15.0  18.0
 21.0  24.0  27.0

대신에

In [35]:
A .+ 2 .* f.(A) ./ A
Out[35]:
3×3 Array{Float64,2}:
  3.0   6.0   9.0
 12.0  15.0  18.0
 21.0  24.0  27.0

를 쓸 수 있다. 그리고 두 개가 정확하게 같은 결과를 낸다.

연습문제

6.1

input 값에 1을 더하는 함수로 더하기_일이라는 함수를 만들어보자.

In [36]:
function 더하기_일1()
    +1
end
Out[36]:
더하기_일1 (generic function with 1 method)
In [37]:
더하기_일1(1)
Out[37]:
2
In [38]:
더하기_일2() = +1
Out[38]:
더하기_일2 (generic function with 1 method)
In [39]:
더하기_일2(1)
Out[39]:
2
In [40]:
더하기_일3 =  -> +1
Out[40]:
#15 (generic function with 1 method)
In [41]:
더하기_일3(1)
Out[41]:
2

6.2

map 또는 broadcast 함수를 사용하여 행렬 A의 모든 요소를 1 씩 증가시키고 변수 A1에 할당해보자.

In [42]:
A1 = map(더하기_일1, A)
Out[42]:
3×3 Array{Int64,2}:
 2  3   4
 5  6   7
 8  9  10
In [43]:
A1 = broadcast(더하기_일2, A)
Out[43]:
3×3 Array{Int64,2}:
 2  3   4
 5  6   7
 8  9  10

6.3

broadcast dot 구문을 사용하여 행렬 A1의 모든 요소를 1 씩 증가시키고 변수 A2에 저장해보자.

In [44]:
A2 = 더하기_일3.(A1)
Out[44]:
3×3 Array{Int64,2}:
 3   4   5
 6   7   8
 9  10  11
In [45]:
A2 = A1 .+ 1
Out[45]:
3×3 Array{Int64,2}:
 3   4   5
 6   7   8
 9  10  11