일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Actor
- 파이썬 데이터분석
- Play2
- Akka
- play2 강좌
- 스칼라 동시성
- 플레이프레임워크
- 이더리움
- 스위프트
- 그라파나
- hyperledger fabric
- 파이썬 머신러닝
- 하이브리드앱
- 안드로이드 웹뷰
- 파이썬
- 파이썬 동시성
- 스칼라 강좌
- Hyperledger fabric gossip protocol
- play 강좌
- Golang
- 스칼라
- 엔터프라이즈 블록체인
- 하이퍼레저 패브릭
- Play2 로 웹 개발
- CORDA
- 블록체인
- 파이썬 강좌
- 주키퍼
- akka 강좌
- Adapter 패턴
- Today
- Total
HAMA 블로그
스칼라 강좌 (14) - 함수 와 메소드 본문
함수와 메소드
스칼라에서 함수종류
특성
* 스칼라 메소드 파라미터에서 중요한 점은 이들이 val 이라는것이다.
def add(b : Byte) : Unit = {
b = 1 // 파라미터가 val 이라 불가능 !
sum += b
}
* return 을 명시적으로 사용하지 말라.
스칼라는 메소드가 한 값을 계산하는 표현식인 것 처럼 생각하는걸 권장한다.
* 메소드가 오직 하나의 표현식만 계산하는 경우 {} 중괄호를 없앨 수 있다.
def add(b:Byte) : Unit = sum += b
* 메소드의 결과 타입이 Unit 인 경우는 부수 효과를 위해 사용한다는 뜻이다.
* Unit 생략하고 = 를 없애서 {} 로 메소드를 나타낼 수 있는데, 이건 프로시저 처럼 보이게 하는데
프로시저란 오직 부수 효과를 얻기 위해서만 사용하는 메소드를 의미한다.
즉 def add(b: Byte) { sum += b} 처럼 사용한다는 뜻이데, 여기서 함정이 있는게
def add2 () { " test string " } 이건 Unit 를 리턴하며
def add3 () = { " test string " } 이건 String 을 리턴한다는 것이다.
즉 프로시저 처럼 작성하면 리턴이 무조건 Unit 이 된다는점~
* 표현식 블럭 즉 {} 를 이용해서 호출도 가능하다. 블럭에서 실행된 값이 매개변수로 들어감
(스칼라에서 이 문법 익숙해지기 전까지 매우 헤깔림)
def formatEuro (amt : Double) = f"$amt%.2f"
formatEuro { val rate = 1.32; 0.235 + 0.7123 + rate * 5.32 } <-- 이런식으로 '호출'
예제 1
import scala.io.Source
object LongLines {
def processFile(filename : String, width : Int )
{
val source = Source.fromFile(filename)
for (line <- source.getLines())
processLine(filename, width, line)
}
private def processLine(filename: String, width: Int, line: String) {
if (line.length > width)
println(filename + ": " + line.trim)
}
}
두개의 메소드로 이루어져 있다.
지역 함수
특성
함수안에 함수를 정의 할 수 있다.
예제 2 (예제 1 의 변형 )
import scala.io.Source
object LongLines {
def processFile(filename : String, width : Int ) {
def processLine(filename: String, width: Int, line: String) {
if (line.length > width)
println(filename + ": " + line.trim)
}
val source = Source.fromFile(filename)
for (line <- source.getLines())
processLine(filename, width, line)
}
}
도우미 함수들은 프로그램의 네임스페이스를 오염 시킬 수 있기 때문에 자바에서는 주로 비공개 메소드를 이용해 이런 목적을 달성하지만 스칼라에서는 함수안에 함수를 정의해서 처리 하기도 한다.
함수안의 함수는 그 외부 함수안에서만 접근 할 수 있다.
예제 3 (예제 2 의 개선 )
import scala.io.Source
object LongLines {
def processFile(filename : String, width : Int ) {
def processLine(line: String) {
if (line.length > width)
println(filename + ": " + line.trim)
}
val source = Source.fromFile(filename)
for (line <- source.getLines())
processLine(filename, width, line)
}
}
내부의 함수에서 바깥 함수의 인자를 사용한다.
1급 계층 함수
특성
스칼라는 1급 함수 (first class function) 를 제공한다. 1급이란 말처럼 상위계층 즉 모든게 가능한.. 할당도 가능하고 , 인자로도 넘길 수 있고, 반환도 가능한 애들 말한다.
이들은 이름 없이 리터럴로 표기해 값 처럼 주고 받을 수 있다.
함수 리터럴은 클래스로 컴파일하는데, 해당 클래스를 실행 시점에 인스턴스화하면 함수 값이 된다.
모든 함수 값은 FunctionN 트레이트 중 하나를 확장한 클래스로부터 만든 인스턴스이다.
예를들어 Function() 은 인자가 없는 함수고, Function1 은 인자가 1개인 식이다.
여기에는 함수 호출시 사용하는 apply 메소드가 들어 있다.
다음 예와 같다.
1
2
3
4
5
|
trait Function1[ T1, R] extends AnyRef { ... def apply( v1 :T1) : R ... } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class Last( init : Int ) extends Function1[Int,Int]{ var last = init def apply( x :Int ) : Int = { val retval = last last = x retval } } val last : Int => Int = new Last( 1 ) val x = last( 5 ) // x == 1 val y = last( 5 ) // y == 5 val z = last( 8 ) // z == 5 ... val a = last( 5 ) // a == 8 |
함수 리터럴 (간단 예제)
(x: Int) => x + 1
정수 x 를 x + 1 로 매핑하는 함수이다.
함수 값은 객체이기 때문에 원하면 변수에 저장 할 수 있다.
동시에 함수 값은 엄연히 함수이기도 하다. ( 괄호를 이용해 실행 할 수 있다는 야그)
var increase = (x : Int) => x + 1 // 함수를 increase 변수에 저장가능!
increase(10) // 호출 가능 ! 내부적으론 apply 메소드가 사용됨.
함수 리터럴 ( 둘 이상의 문장 예제)
increate = (x: Int ) => {
println("...")
println("...")
x + 1
}
둘 이상의 문장이 필요하면 {} 중괄호로 감싸서 블록으로 만든다.
함수 리터럴 ( 매개변수로 사용)
val numbers = List (1,2,3,4,5)
numbers .foreach((x:Int) => println(x))
다음처럼 함수의 매개변수로 들어가서 사용된다.
이제는 대부분의 언어에서 상식이 되버린 map, filter 함수에서도 사용된다.
scala> numbers.map((i: Int) => i * 2) res0: List[Int] = List(2, 4, 6, 8, 10)
scala> numbers.filter((i: Int) => i % 2 == 0) res0: List[Int] = List(2, 4)
간단한 형태의 함수 리터럴
scala> numbers.filter( i => i % 2 == 0) res0: List[Int] = List(2, 4)
애초에 컬렉션에서 i 는 정수라는 하실을 알기 때문에 굳이 Int 라고 하지 않아도 된다.
타깃 타이핑이라고 하며 () 도 생략 가능하다.
더 간단한 형태의 함수 리터럴
scala> numbers.filter( _ > 4) res0: List[Int] = List(5)
_ 는 함수가 호출 시 전달받은 인자로 하나씩 채워져서 계산될 것이다.
기타 특성
반복 파라미터
def echo (args: String*) = for (arg <-args) println(arg) // 매개변수에 * 를 표시함으로써
echo("hello", "world") // 요렇게 인자의 갯수를 자유롭게 할 수 있다.
이름 있는 인자
def speed(distance: Float, time: Flot) : Flot = distance / time
speed(100,10) // 일반적 사용
speed(time = 10, distance = 100) // 이름을 함께 사용, 순서를 바꾸어서 사용해도 된다.
디폴트 값
def printTime(out: java.io.PrintStream = Console.out) = out.println("time = " + System.currentTimeMillis())
printTime() // 이렇게 호출하면 자동적으로 디폴트로 out 은 Console.out 이 된다.
클로저
( 나중에 따로 다루겠습니다 )
def 와 val 의 차이
def even: Int => Boolean = _ % 2 == 0
val even: Int => Boolean = _ % 2 == 0
even(10) // 둘다 이렇게 호출 가능
위에서 배운 바와 같이 함수는 이렇게 표현 가능한데요. 둘의 차이는 멀까요?
메소드 def even 은 호출 되었을때 평가되며 새로운 Function1 객체를 매 호출시마다 생성합니다.
반면 val 은 정의 되었을때 평가됩니다.
val test: () => Int = { // 이미 평가됨 (리턴값이 항상 같을 경우 사용 할 수 있다)
val r = util.Random.nextInt () => r
}
test() // Int = -1049057402
test() // Int = -1049057402
- 동일한 값
def test: () => Int = {
// 호출할때마다 평가가 달라짐
val r = util.Random.nextInt () => r
}
test() // Int = -240885810
test() // Int = -1002157461 - 값이 달라 졌다.
* lazy val 로 선언하면 이름에서 느껴지는것 처럼 정의 되었을대 평가되는게 아니라 처음 호출시 평가됩니다.
스칼라에서의 함수 호출
스칼라에서는 함수호출시 () 대신 {} 중괄호를 사용 할 수 있다. 단 !!! 인자가 하나일 경우 !!
def formatEuro (amt : Double) = {.......} <-- 와 같은 Double 형의 매개변수 하나를 받는 함수가 있을 시formatEuro { val rate = 1.32; 0.235 + 0.7123 + rate * 5.32 } <-- 이렇게 호출 할 수 있는데, {} 블럭 안에서 평가된 rate 가 매개변수로 들어가게 된다.
더 간단하게 예를 들면
println ("Hello World") 대신해서 println { "Hello World" } 를 사용 할 수 있다는 의미이다.
스칼라에서 이렇게 한 이유는 프로그래머가 중괄호{} 내부에서 함수 리터럴을 직접 사용하도록 하기 위해서인데, 이렇게 작성한 메소드는 호출 시 좀 더 제어 추상화 구문과 비슷해진다. 좀 더 수준높은 예제를 들어 보면
def withPrintWriter(file: File, op: PrintWriter => Unit ) {
val writer = new PrintWriter(file)try { op (writer)} finally { writer.close()}
}
이런 함수를 호출 할 때는
withPrintWriter ( new File ("date.txt") , writer => writer.println(new java.util.Date) )
이렇게 호출 하며, 여기서는 인자가 2개이기 때문에 { } 를 사용할 수 없다. 하지만 !!!!
def withPrintWriter(file: File) (op: PrintWriter => Unit ) { ...
}
이러한 함수라면 {} 를 사용 할 수 있다.
val file = new File("date.txt")withPrintWriter (file) { writer => writer.println(new java.util.Date) }
왜 이게 되냐면 커링을 통해서 file 을 매개변수로 먼저 넣어진 새로운 함수가 생겨졌고, 그 새로운 함수는 매개변수 하나를 필요로 하는 함수이기 때문이다.!! 보통 (변수,함수) 매개변수 조합일 때 많이 사용된다.
def foldLeft[B](z: B)(op: (B, A) => B): B = {
var result = z
this foreach (x => result = op(result, x))
result
}
Vector(1, 2, 3).foldLeft(Vector[Int]()) { (is, i) =>
is :+ i * i
}
(curry vs partially applying 이라는 헥깔림 주제도 있다)
웹 MVC 컨트롤러에서의 예 (Play for Scala)
스프링에서 컨트롤러 함수의 예가 다음과 같은 모습을 취하는 반면
@RequestMapping(method = RequestMethod.GET)
public String printHello(ModelMap model) {
model.addAttribute("message", "Hello Spring MVC Framework!");
return "hello";
}
스칼라언어 기반 Play 프레임워크 웹개발에서는 (위의 자바 예와 동일한 내용의 예가 아닙니다)
def doSomething = Action{ Ok.apply(views.html.index("Hi there")) }
이런 모양새로 이루어지는데 여기서 Action 이 무엇일까요?
위의 예는 다음과 같이 풀어지게 되는데요
def doSomething: Action[AnyContent] = Action.apply({ Ok.apply(views.html.index("Hi there")) })
위의 apply 메소드의 정의는 apply(block: => Result) 이라 익명의 함수가 들어가게 됩니다.
Action.apply 함수의 () 는 생략가능이고 apply 함수내에는 인자가 없고 Result 를 리턴하는 함수가 들어가네요.
이 함수 말이죠.
{ Ok.apply(views.html.index("Hi there")) }
그리고 doSomething 메소드는 Action[AnyContent] 클래스의 인스턴스를 리턴합니다.
항상 같은 값을 리턴하기때문에 def 을 빼도 됩니다. 즉 메소드가 아니라 val 이어도 된다는 말.
Action { request => Ok("Got request [" + request + "]") }
처럼 인자가 있는 함수가 들어가기도 합니다.
'Scala' 카테고리의 다른 글
스칼라 강좌 (16) - Enumerations (열거) (0) | 2016.08.06 |
---|---|
스칼라 강좌 (15) - Getter / Setter (0) | 2016.07.31 |
스칼라 강좌 (13) - 클래스와 객체 (0) | 2016.07.16 |
스칼라 강좌 (12) - 객체의 동일성 (0) | 2016.07.08 |
스칼라 강좌 (11) - while 루프 와 재귀 (0) | 2016.07.03 |