F#에서 함수 사용하기

F#에서 함수 사용하기 (Using Functions)

F#에서는 함수가 다른 내장 값들과 똑같이 다뤄져요. 함수에 이름을 붙이고, 리스트 같은 자료 구조에 담고, 다른 함수의 인자로 넘기고, 함수가 함수를 돌려주는 것까지 모두 가능하죠. 이렇게 함수를 값처럼 자유롭게 다루는 성질을 "함수는 1급 값(first-class value)이다"라고 해요. 이번 글에서는 F# 함수를 선언하고 활용하는 여러 방법을 하나씩 살펴볼게요.

출처: https://learn.microsoft.com/en-us/dotnet/fsharp/tutorials/using-functions

본문

간단한 함수 정의

가장 간단한 함수 정의는 이렇게 생겼어요.

let f x = x + 1

앞의 예에서 함수 이름은 f, 인자는 타입이 intx, 함수 본문은 x + 1, 그리고 반환값의 타입은 int예요.

F#의 핵심 특징 하나는 함수가 1급 지위(first-class status)를 가진다는 거예요. 그래서 다른 내장 타입의 값으로 할 수 있는 일은 함수로도 비슷한 수준의 노력으로 다 할 수 있어요.

  • 함수 값에 이름을 붙일 수 있어요.
  • 함수를 리스트 같은 자료 구조에 저장할 수 있어요.
  • 함수 호출에서 함수를 인자로 넘길 수 있어요.
  • 함수 호출에서 함수를 반환할 수 있어요.

값에 이름 붙이기

함수가 1급 값이라면, 정수·문자열 같은 내장 타입에 이름을 붙이듯 함수에도 이름을 붙일 수 있어야 해요. 함수형 프로그래밍 문헌에서는 이걸 "식별자를 값에 바인딩한다(binding an identifier to a value)"고 불러요. F#은 let 바인딩으로 값에 이름을 붙여요: let <identifier> = <value>. 다음 코드는 그 예시 두 개를 보여줘요.

// Integer and string.
let num = 10
let str = "F#"

함수에도 똑같이 쉽게 이름을 붙일 수 있어요. 다음 예는 식별자 squareIt람다 식 fun n -> n * n에 바인딩해서 squareIt이라는 함수를 정의해요. squareIt은 매개 변수 n 하나를 받고 그 제곱을 돌려줘요.

let squareIt = fun n -> n * n

F#은 같은 결과를 더 적게 타이핑해서 얻을 수 있는 다음과 같은 간결한 문법도 제공해요.

let squareIt2 n = n * n

이어지는 예들은 대부분 첫 번째 스타일(let <function-name> = <lambda-expression>)을 쓸 거예요. 함수 선언이 다른 타입의 값 선언과 얼마나 비슷한지 강조하기 위해서죠. 하지만 이름 붙은 함수는 전부 간결한 문법으로도 쓸 수 있어요. 일부 예는 두 방식 모두로 작성했어요.

값을 자료 구조에 저장하기

1급 값은 자료 구조에 저장할 수 있어요. 다음 코드는 값을 리스트와 튜플에 저장하는 예시예요.

// Lists.
// Storing integers and strings.
let integerList = [ 1; 2; 3; 4; 5; 6; 7 ]
let stringList = [ "one"; "two"; "three" ]

// You cannot mix types in a list. The following declaration causes a
// type-mismatch compiler error.
//let failedList = [ 5; "six" ]

// In F#, functions can be stored in a list, as long as the functions
// have the same signature.
// Function doubleIt has the same signature as squareIt, declared previously.
//let squareIt = fun n -> n * n
let doubleIt = fun n -> 2 * n

// Functions squareIt and doubleIt can be stored together in a list.
let funList = [ squareIt; doubleIt ]

// Function squareIt cannot be stored in a list together with a function
// that has a different signature, such as the following body mass
// index (BMI) calculator.
let BMICalculator = fun ht wt ->
                    (float wt / float (squareIt ht)) * 703.0

// The following expression causes a type-mismatch compiler error.
//let failedFunList = [ squareIt; BMICalculator ]

// Tuples.
// Integers and strings.
let integerTuple = ( 1, -7 )
let stringTuple = ( "one", "two", "three" )

// A tuple does not require its elements to be of the same type.
let mixedTuple = ( 1, "two", 3.3 )

// Similarly, function elements in tuples can have different signatures.
let funTuple = ( squareIt, BMICalculator )

// Functions can be mixed with integers, strings, and other types in
// a tuple. Identifier num was declared previously.
//let num = 10
let moreMixedTuple = ( num, "two", 3.3, squareIt )

튜플에 저장된 함수 이름이 정말로 함수로 평가되는지 확인하기 위해, 다음 예는 fstsnd 연산자를 써서 튜플 funAndArgTuple에서 첫 번째와 두 번째 요소를 꺼내요. 튜플의 첫 번째 요소는 squareIt, 두 번째 요소는 num이에요. 식별자 num은 앞선 예에서 정수 10에 바인딩되어 있고, 그 값은 squareIt 함수의 유효한 인자예요. 두 번째 식은 튜플의 첫 번째 요소를 두 번째 요소에 적용해요: squareIt num.

// You can pull a function out of a tuple and apply it. Both squareIt and num
// were defined previously.
let funAndArgTuple = (squareIt, num)

// The following expression applies squareIt to num, returns 100, and
// then displays 100.
System.Console.WriteLine((fst funAndArgTuple)(snd funAndArgTuple))

마찬가지로, 식별자 num과 정수 10이 서로 바꿔 쓸 수 있듯이 식별자 squareIt과 람다 식 fun n -> n * n도 서로 바꿔 쓸 수 있어요.

// Make a tuple of values instead of identifiers.
let funAndArgTuple2 = ((fun n -> n * n), 10)

// The following expression applies a squaring function to 10, returns
// 100, and then displays 100.
System.Console.WriteLine((fst funAndArgTuple2)(snd funAndArgTuple2))

값을 인자로 넘기기

어떤 언어에서 값이 1급 지위를 가진다면, 그 값을 함수의 인자로 넘길 수 있어요. 예를 들어 정수와 문자열을 인자로 넘기는 건 아주 흔한 일이죠. 다음 코드는 F#에서 정수와 문자열을 인자로 넘기는 모습을 보여줘요.

// An integer is passed to squareIt. Both squareIt and num are defined in
// previous examples.
//let num = 10
//let squareIt = fun n -> n * n
System.Console.WriteLine(squareIt num)

// String.
// Function repeatString concatenates a string with itself.
let repeatString = fun s -> s + s

// A string is passed to repeatString. HelloHello is returned and displayed.
let greeting = "Hello"
System.Console.WriteLine(repeatString greeting)

함수가 1급 지위를 가진다면, 함수도 같은 방식으로 인자로 넘길 수 있어야 해요. 기억하세요, 이것이 고차 함수(higher-order function)의 첫 번째 특징이에요.

다음 예에서 함수 applyIt은 매개 변수 oparg 두 개를 받아요. 매개 변수 하나를 받는 함수를 op로 보내고, 그 함수가 받을 적절한 인자를 arg로 보내면, applyItoparg에 적용한 결과를 돌려줘요. 아래 예는 함수 인자와 정수 인자를 모두 이름을 써서 똑같은 방식으로 보내요.

// Define the function, again using lambda expression syntax.
let applyIt = fun op arg -> op arg

// Send squareIt for the function, op, and num for the argument you want to
// apply squareIt to, arg. Both squareIt and num are defined in previous
// examples. The result returned and displayed is 100.
System.Console.WriteLine(applyIt squareIt num)

// The following expression shows the concise syntax for the previous function
// definition.
let applyIt2 op arg = op arg

// The following line also displays 100.
System.Console.WriteLine(applyIt2 squareIt num)

함수를 다른 함수의 인자로 보낼 수 있는 능력은 map이나 filter 같은 함수형 프로그래밍 언어의 흔한 추상화의 기반이 돼요. 예를 들어 map 연산은 고차 함수예요. 리스트를 훑어가며 각 요소에 무언가를 하고 결과의 리스트를 돌려주는 함수들이 공유하는 계산을 담아두죠. 정수 리스트의 각 요소를 1씩 늘리고 싶을 수도, 각 요소를 제곱하고 싶을 수도, 문자열 리스트의 각 요소를 대문자로 바꾸고 싶을 수도 있어요. 이 계산에서 오류가 나기 쉬운 부분은 리스트를 훑고 돌려줄 결과 리스트를 만들어 내는 재귀 과정이에요. 그 부분이 매핑 함수에 담겨 있어요. 특정 응용 프로그램에서 네가 직접 써야 하는 건 리스트의 각 요소에 따로 적용하고 싶은 함수(더하기, 제곱, 대소문자 바꾸기)뿐이에요. 그리고 그 함수를 앞선 예에서 squareItapplyIt에 보낸 것처럼 매핑 함수의 인자로 보내면 돼요.

F#은 리스트, 배열, 시퀀스를 포함해 대부분의 컬렉션 타입에 대해 map 메서드를 제공해요. 다음 예는 리스트를 사용해요. 문법은 List.map <the function> <the list>예요.

// List integerList was defined previously:
//let integerList = [ 1; 2; 3; 4; 5; 6; 7 ]

// You can send the function argument by name, if an appropriate function
// is available. The following expression uses squareIt.
let squareAll = List.map squareIt integerList

// The following line displays [1; 4; 9; 16; 25; 36; 49]
printfn "%A" squareAll

// Or you can define the action to apply to each list element inline.
// For example, no function that tests for even integers has been defined,
// so the following expression defines the appropriate function inline.
// The function returns true if n is even; otherwise it returns false.
let evenOrNot = List.map (fun n -> n % 2 = 0) integerList

// The following line displays [false; true; false; true; false; true; false]
printfn "%A" evenOrNot

더 자세한 내용은 Lists를 참고해요.

함수 호출에서 값 반환하기

마지막으로, 어떤 언어에서 함수가 1급 지위를 가진다면, 정수나 문자열 같은 다른 타입을 반환하듯 함수도 함수 호출의 값으로 반환할 수 있어야 해요.

다음 함수 호출은 정수를 반환하고 출력해요.

// Function doubleIt is defined in a previous example.
//let doubleIt = fun n -> 2 * n
System.Console.WriteLine(doubleIt 3)
System.Console.WriteLine(squareIt 4)

다음 함수 호출은 문자열을 반환해요.

// str is defined in a previous section.
//let str = "F#"
let lowercase = str.ToLower()

다음 함수 호출은 인라인으로 선언되어 Boolean 값을 반환해요. 표시되는 값은 True가 돼요.

System.Console.WriteLine((fun n -> n % 2 = 1) 15)

함수를 함수 호출의 값으로 반환하는 능력은 고차 함수의 두 번째 특징이에요. 다음 예에서 checkFor는 인자 item 하나를 받고 새 함수를 값으로 반환하는 함수로 정의돼요. 반환된 함수는 리스트를 인자 lst로 받아 lst 안에서 item을 찾아요. item이 있으면 true를, 없으면 false를 반환해요. 앞선 절과 마찬가지로 다음 코드는 제공된 리스트 함수 List.exists를 사용해 리스트를 검색해요.

let checkFor item =
    let functionToReturn = fun lst ->
                           List.exists (fun a -> a = item) lst
    functionToReturn

다음 코드는 checkFor를 써서 인자 하나(리스트)를 받고 그 리스트에서 7을 찾는 새 함수를 만들어요.

// integerList and stringList were defined earlier.
//let integerList = [ 1; 2; 3; 4; 5; 6; 7 ]
//let stringList = [ "one"; "two"; "three" ]

// The returned function is given the name checkFor7.
let checkFor7 = checkFor 7

// The result displayed when checkFor7 is applied to integerList is True.
System.Console.WriteLine(checkFor7 integerList)

// The following code repeats the process for "seven" in stringList.
let checkForSeven = checkFor "seven"

// The result displayed is False.
System.Console.WriteLine(checkForSeven stringList)

다음 예는 F#에서 함수가 1급 지위를 가진다는 점을 활용해, 두 함수 인자의 합성(composition)을 반환하는 compose 함수를 선언해요.

// Function compose takes two arguments. Each argument is a function
// that takes one argument of the same type. The following declaration
// uses lambda expression syntax.
let compose =
    fun op1 op2 ->
        fun n ->
            op1 (op2 n)

// To clarify what you are returning, use a nested let expression:
let compose2 =
    fun op1 op2 ->
        // Use a let expression to build the function that will be returned.
        let funToReturn = fun n ->
                            op1 (op2 n)
        // Then just return it.
        funToReturn

// Or, integrating the more concise syntax:
let compose3 op1 op2 =
    let funToReturn = fun n ->
                        op1 (op2 n)
    funToReturn

[!NOTE] 더 짧은 버전은 이어지는 "커리된 함수(Curried Functions)" 절을 참고해요.

다음 코드는 각각 같은 타입의 인자 하나를 받는 두 함수를 compose에 인자로 보내요. 반환값은 두 함수 인자의 합성인 새 함수예요.

// Functions squareIt and doubleIt were defined in a previous example.
let doubleAndSquare = compose squareIt doubleIt

// The following expression doubles 3, squares 6, and returns and
// displays 36.
System.Console.WriteLine(doubleAndSquare 3)

let squareAndDouble = compose doubleIt squareIt

// The following expression squares 3, doubles 9, returns 18, and
// then displays 18.
System.Console.WriteLine(squareAndDouble 3)

[!NOTE] F#은 함수를 합성하는 두 연산자 <<>>를 제공해요. 예를 들어 let squareAndDouble2 = doubleIt << squareIt는 앞선 예의 let squareAndDouble = compose doubleIt squareIt와 동일해요.

함수 호출의 값으로 함수를 반환하는 다음 예는 간단한 추측 게임을 만들어요. 게임을 만들려면 상대방이 맞히길 원하는 값을 target으로 보내면서 makeGame을 호출해요. makeGame 함수의 반환값은 인자 하나(추측값)를 받아 그 추측이 맞는지 알려주는 함수예요.

let makeGame target =
    // Build a lambda expression that is the function that plays the game.
    let game = fun guess ->
                   if guess = target then
                      System.Console.WriteLine("You win!")
                   else
                      System.Console.WriteLine("Wrong. Try again.")
    // Now just return it.
    game

다음 코드는 target에 값 7을 보내면서 makeGame을 호출해요. 식별자 playGame은 반환된 람다 식에 바인딩돼요. 따라서 playGameguess의 값을 인자 하나로 받는 함수가 돼요.

let playGame = makeGame 7

// Send in some guesses.
playGame 2
playGame 9
playGame 7

// Output:
// Wrong. Try again.
// Wrong. Try again.
// You win!

// The following game specifies a character instead of an integer for target.
let alphaGame = makeGame 'q'
alphaGame 'c'
alphaGame 'r'
alphaGame 'j'
alphaGame 'q'

// Output:
// Wrong. Try again.
// Wrong. Try again.
// Wrong. Try again.
// You win!

커리된 함수 (Curried Functions)

앞선 절의 많은 예는 F# 함수 선언에 내재된 커링(currying)을 활용하면 더 간결하게 쓸 수 있어요. 커링은 매개 변수가 둘 이상인 함수를, 각각 매개 변수가 하나인 중첩 함수들의 연속으로 바꾸는 과정이에요. F#에서는 매개 변수가 둘 이상인 함수가 본질적으로 커리되어 있어요. 예를 들어 앞선 절의 compose는 다음처럼 매개 변수 세 개의 간결한 스타일로 쓸 수 있어요.

let compose4 op1 op2 n = op1 (op2 n)

하지만 그 결과는 compose4curried에서 볼 수 있듯이, 매개 변수 하나를 받아 또 다른 매개 변수 하나짜리 함수를 반환하고, 그 함수가 다시 매개 변수 하나짜리 함수를 반환하는 형태의 함수예요.

let compose4curried =
    fun op1 ->
        fun op2 ->
            fun n -> op1 (op2 n)

이 함수에는 여러 방식으로 접근할 수 있어요. 다음 예는 각각 18을 반환하고 출력해요. 어떤 예에서든 compose4compose4curried로 바꿔도 돼요.

// Access one layer at a time.
System.Console.WriteLine(((compose4 doubleIt) squareIt) 3)

// Access as in the original compose examples, sending arguments for
// op1 and op2, then applying the resulting function to a value.
System.Console.WriteLine((compose4 doubleIt squareIt) 3)

// Access by sending all three arguments at the same time.
System.Console.WriteLine(compose4 doubleIt squareIt 3)

함수가 여전히 이전처럼 동작하는지 확인하려면 원래 테스트 케이스를 다시 시도해봐요.

let doubleAndSquare4 = compose4 squareIt doubleIt

// The following expression returns and displays 36.
System.Console.WriteLine(doubleAndSquare4 3)

let squareAndDouble4 = compose4 doubleIt squareIt

// The following expression returns and displays 18.
System.Console.WriteLine(squareAndDouble4 3)

[!NOTE] 매개 변수를 튜플로 감싸면 커링을 제한할 수 있어요. 자세한 내용은 Parameters and Arguments의 "Parameter Patterns"를 참고해요.

다음 예는 암시적 커링을 써서 makeGame의 더 짧은 버전을 작성해요. makeGamegame 함수를 어떻게 만들고 반환하는지에 대한 세부 사항은 이 형식에서 덜 명시적이지만, 원래 테스트 케이스로 확인해보면 결과가 같다는 걸 알 수 있어요.

let makeGame2 target guess =
    if guess = target then
       System.Console.WriteLine("You win!")
    else
       System.Console.WriteLine("Wrong. Try again.")

let playGame2 = makeGame2 7
playGame2 2
playGame2 9
playGame2 7

let alphaGame2 = makeGame2 'q'
alphaGame2 'c'
alphaGame2 'r'
alphaGame2 'j'
alphaGame2 'q'

커링에 대한 자세한 내용은 Functions의 "Partial Application of Arguments"를 참고해요.

식별자와 함수 정의는 서로 바꿔 쓸 수 있어요

앞선 예의 변수 이름 num은 정수 10으로 평가돼요. 그래서 num이 유효한 곳이라면 10도 유효하다는 게 당연해요. 함수 식별자와 그 값도 마찬가지예요. 함수의 이름을 쓸 수 있는 곳이라면 그 함수가 바인딩된 람다 식도 쓸 수 있어요.

다음 예는 isNegative라는 Boolean 함수를 정의하고, 함수의 이름과 정의를 서로 바꿔 사용해요. 다음 세 예는 모두 False를 반환하고 출력해요.

let isNegative = fun n -> n < 0

// This example uses the names of the function argument and the integer
// argument. Identifier num is defined in a previous example.
//let num = 10
System.Console.WriteLine(applyIt isNegative num)

// This example substitutes the value that num is bound to for num, and the
// value that isNegative is bound to for isNegative.
System.Console.WriteLine(applyIt (fun n -> n < 0) 10)

한 걸음 더 나아가서, applyIt이 바인딩된 값으로 applyIt을 치환해볼게요.

System.Console.WriteLine((fun op arg -> op arg) (fun n -> n < 0)  10)

F#에서 함수는 1급 값이에요

앞선 절의 예들은 F#의 함수가 1급 값이 되기 위한 기준을 충족한다는 걸 보여줘요.

  • 식별자를 함수 정의에 바인딩할 수 있어요.
let squareIt = fun n -> n * n
  • 함수를 자료 구조에 저장할 수 있어요.
let funTuple2 = ( BMICalculator, fun n -> n * n )
  • 함수를 인자로 넘길 수 있어요.
let increments = List.map (fun n -> n + 1) [ 1; 2; 3; 4; 5; 6; 7 ]
  • 함수를 함수 호출의 값으로 반환할 수 있어요.
let checkFor item =
    let functionToReturn = fun lst ->
                           List.exists (fun a -> a = item) lst
    functionToReturn

F#에 대한 더 자세한 내용은 F# Language Reference를 참고해요.

전체 예제

다음 코드는 이 페이지의 모든 예제를 담고 있어요.

// ** GIVE THE VALUE A NAME **
// Integer and string.
let num = 10
let str = "F#"
let squareIt = fun n -> n * n
let squareIt2 n = n * n

// ** STORE THE VALUE IN A DATA STRUCTURE **
// Lists.
// Storing integers and strings.
let integerList = [ 1; 2; 3; 4; 5; 6; 7 ]
let stringList = [ "one"; "two"; "three" ]

// You cannot mix types in a list. The following declaration causes a
// type-mismatch compiler error.
//let failedList = [ 5; "six" ]

// In F#, functions can be stored in a list, as long as the functions
// have the same signature.
// Function doubleIt has the same signature as squareIt, declared previously.
//let squareIt = fun n -> n * n
let doubleIt = fun n -> 2 * n

// Functions squareIt and doubleIt can be stored together in a list.
let funList = [ squareIt; doubleIt ]

// Function squareIt cannot be stored in a list together with a function
// that has a different signature, such as the following body mass
// index (BMI) calculator.
let BMICalculator = fun ht wt ->
                    (float wt / float (squareIt ht)) * 703.0

// The following expression causes a type-mismatch compiler error.
//let failedFunList = [ squareIt; BMICalculator ]

// Tuples.
// Integers and strings.
let integerTuple = ( 1, -7 )
let stringTuple = ( "one", "two", "three" )

// A tuple does not require its elements to be of the same type.
let mixedTuple = ( 1, "two", 3.3 )

// Similarly, function elements in tuples can have different signatures.
let funTuple = ( squareIt, BMICalculator )

// Functions can be mixed with integers, strings, and other types in
// a tuple. Identifier num was declared previously.
//let num = 10
let moreMixedTuple = ( num, "two", 3.3, squareIt )

// You can pull a function out of a tuple and apply it. Both squareIt and num
// were defined previously.
let funAndArgTuple = (squareIt, num)

// The following expression applies squareIt to num, returns 100, and
// then displays 100.
System.Console.WriteLine((fst funAndArgTuple)(snd funAndArgTuple))

// Make a list of values instead of identifiers.
let funAndArgTuple2 = ((fun n -> n * n), 10)

// The following expression applies a squaring function to 10, returns
// 100, and then displays 100.
System.Console.WriteLine((fst funAndArgTuple2)(snd funAndArgTuple2))

// ** PASS THE VALUE AS AN ARGUMENT **
// An integer is passed to squareIt. Both squareIt and num are defined in
// previous examples.
//let num = 10
//let squareIt = fun n -> n * n
System.Console.WriteLine(squareIt num)

// String.
// Function repeatString concatenates a string with itself.
let repeatString = fun s -> s + s

// A string is passed to repeatString. HelloHello is returned and displayed.
let greeting = "Hello"
System.Console.WriteLine(repeatString greeting)

// Define the function, again using lambda expression syntax.
let applyIt = fun op arg -> op arg

// Send squareIt for the function, op, and num for the argument you want to
// apply squareIt to, arg. Both squareIt and num are defined in previous
// examples. The result returned and displayed is 100.
System.Console.WriteLine(applyIt squareIt num)

// The following expression shows the concise syntax for the previous function
// definition.
let applyIt2 op arg = op arg

// The following line also displays 100.
System.Console.WriteLine(applyIt2 squareIt num)

// List integerList was defined previously:
//let integerList = [ 1; 2; 3; 4; 5; 6; 7 ]

// You can send the function argument by name, if an appropriate function
// is available. The following expression uses squareIt.
let squareAll = List.map squareIt integerList

// The following line displays [1; 4; 9; 16; 25; 36; 49]
printfn "%A" squareAll

// Or you can define the action to apply to each list element inline.
// For example, no function that tests for even integers has been defined,
// so the following expression defines the appropriate function inline.
// The function returns true if n is even; otherwise it returns false.
let evenOrNot = List.map (fun n -> n % 2 = 0) integerList

// The following line displays [false; true; false; true; false; true; false]
printfn "%A" evenOrNot

// ** RETURN THE VALUE FROM A FUNCTION CALL **
// Function doubleIt is defined in a previous example.
//let doubleIt = fun n -> 2 * n
System.Console.WriteLine(doubleIt 3)
System.Console.WriteLine(squareIt 4)

// The following function call returns a string:
// str is defined in a previous section.
//let str = "F#"
let lowercase = str.ToLower()
System.Console.WriteLine((fun n -> n % 2 = 1) 15)

let checkFor item =
    let functionToReturn = fun lst ->
                           List.exists (fun a -> a = item) lst
    functionToReturn

// integerList and stringList were defined earlier.
//let integerList = [ 1; 2; 3; 4; 5; 6; 7 ]
//let stringList = [ "one"; "two"; "three" ]

// The returned function is given the name checkFor7.
let checkFor7 = checkFor 7

// The result displayed when checkFor7 is applied to integerList is True.
System.Console.WriteLine(checkFor7 integerList)

// The following code repeats the process for "seven" in stringList.
let checkForSeven = checkFor "seven"

// The result displayed is False.
System.Console.WriteLine(checkForSeven stringList)

// Function compose takes two arguments. Each argument is a function
// that takes one argument of the same type. The following declaration
// uses lambda expression syntax.
let compose =
    fun op1 op2 ->
        fun n ->
            op1 (op2 n)

// To clarify what you are returning, use a nested let expression:
let compose2 =
    fun op1 op2 ->
        // Use a let expression to build the function that will be returned.
        let funToReturn = fun n ->
                            op1 (op2 n)
        // Then just return it.
        funToReturn

// Or, integrating the more concise syntax:
let compose3 op1 op2 =
    let funToReturn = fun n ->
                        op1 (op2 n)
    funToReturn

// Functions squareIt and doubleIt were defined in a previous example.
let doubleAndSquare = compose squareIt doubleIt

// The following expression doubles 3, squares 6, and returns and
// displays 36.
System.Console.WriteLine(doubleAndSquare 3)

let squareAndDouble = compose doubleIt squareIt

// The following expression squares 3, doubles 9, returns 18, and
// then displays 18.
System.Console.WriteLine(squareAndDouble 3)

let makeGame target =
    // Build a lambda expression that is the function that plays the game.
    let game = fun guess ->
                   if guess = target then
                      System.Console.WriteLine("You win!")
                   else
                      System.Console.WriteLine("Wrong. Try again.")
    // Now just return it.
    game

let playGame = makeGame 7

// Send in some guesses.
playGame 2
playGame 9
playGame 7

// Output:
// Wrong. Try again.
// Wrong. Try again.
// You win!

// The following game specifies a character instead of an integer for target.
let alphaGame = makeGame 'q'
alphaGame 'c'
alphaGame 'r'
alphaGame 'j'
alphaGame 'q'

// Output:
// Wrong. Try again.
// Wrong. Try again.
// Wrong. Try again.
// You win!

// ** CURRIED FUNCTIONS **
let compose4 op1 op2 n = op1 (op2 n)
let compose4curried =
    fun op1 ->
        fun op2 ->
            fun n -> op1 (op2 n)

// Access one layer at a time.
System.Console.WriteLine(((compose4 doubleIt) squareIt) 3)

// Access as in the original compose examples, sending arguments for
// op1 and op2, then applying the resulting function to a value.
System.Console.WriteLine((compose4 doubleIt squareIt) 3)

// Access by sending all three arguments at the same time.
System.Console.WriteLine(compose4 doubleIt squareIt 3)

let doubleAndSquare4 = compose4 squareIt doubleIt

// The following expression returns and displays 36.
System.Console.WriteLine(doubleAndSquare4 3)

let squareAndDouble4 = compose4 doubleIt squareIt

// The following expression returns and displays 18.
System.Console.WriteLine(squareAndDouble4 3)

let makeGame2 target guess =
    if guess = target then
       System.Console.WriteLine("You win!")
    else
       System.Console.WriteLine("Wrong. Try again.")

let playGame2 = makeGame2 7
playGame2 2
playGame2 9
playGame2 7

let alphaGame2 = makeGame2 'q'
alphaGame2 'c'
alphaGame2 'r'
alphaGame2 'j'
alphaGame2 'q'

// ** IDENTIFIER AND FUNCTION DEFINITION ARE INTERCHANGEABLE **
let isNegative = fun n -> n < 0

// This example uses the names of the function argument and the integer
// argument. Identifier num is defined in a previous example.
//let num = 10
System.Console.WriteLine(applyIt isNegative num)

// This example substitutes the value that num is bound to for num, and the
// value that isNegative is bound to for isNegative.
System.Console.WriteLine(applyIt (fun n -> n < 0) 10)

System.Console.WriteLine((fun op arg -> op arg) (fun n -> n < 0)  10)

// ** FUNCTIONS ARE FIRST-CLASS VALUES IN F# **
//let squareIt = fun n -> n * n
let funTuple2 = ( BMICalculator, fun n -> n * n )
let increments = List.map (fun n -> n + 1) [ 1; 2; 3; 4; 5; 6; 7 ]
//let checkFor item =
//    let functionToReturn = fun lst ->
//                           List.exists (fun a -> a = item) lst
//    functionToReturn

더 알아보기