# 게임 세계 둘러보기(Looking Around in our Game World)
The first command we'd want to have is a command that tells us about the location we're standing in. So what would a function need to describe a location in a world? Well, it would need to know the location we want to describe and would need to be able to look at a map and find that location on the map. Here's our function, and it does exactly that:
우리가 갖고 싶은 첫 번째 명령은 우리가 서있는 위치를 알려주는 명령입니다. 그렇다면 함수는 세계의 위치를 설명하는 데 무엇이 필요할까요? 음... 우리가 설명하고 싶은 위치를 알아야 하고 지도를보고 그 위치를 지도에서 찾을 수 있어야 할 것입니다. 우리의 기능은 다음과 같습니다.
(defn describe-location [location game-map]
(first (location game-map)))
The word defn
means, as you'd expect, that we're defining a function. The name of the function is describe-location
and it takes two parameters: a location and a game map. Since these variables appear in the function's parameters, it means they are local and hence unrelated to the global location
and game-map
variables we defined earlier. Note that functions in Lisp are often more like functions in math than in other programming languages: Just like in math, this function does not print stuff for the user to read or pop up a message box: All it does is return a value as a result of the function that contains the description. Let's imagine our location is in the living-room (which, indeed, it is...).
여러분의 예상대로, 단어 defn
는 우리가 함수를 정의하고 있음을 의미합니다. 함수의 이름은 describe-location
이고, 위치와 게임 맵이라는 두 가지 매개 변수를 사용합니다. 이러한 변수는 함수의 매개 변수에 나타나기 때문에, 지역변수임을 의미하며, 이전에 정의한 전역변수 location
및 game-map
변수와 관련이 없습니다. Lisp의 함수는 다른 프로그래밍 언어보다 수학 함수와 더 비슷합니다. 수학에서와 마찬가지로 이 함수는 사용자가 읽도록 내용을 표시하거나, 메시지 상자를 팝업하지 않습니다. 하는 일은 설명이 포함 된 함수의 결과로서 값을 리턴(return)하는 것입니다. 우리의 위치가 거실에 있다고 상상해 봅시다.(정말로...)
Clojure는 매개 변수 목록을 대괄호로 표시합니다. 대부분의 다른 Lisp(계열) 언어는 여기에 괄호가 필요합니다.
이에 대한 설명을 찾기 위해, describe-location
는 먼저 지도(map)에서 거실을 가리키는 지점을 찾아야 합니다. (location game-map)
은 game-map
에서 조회를 수행한 다음, 거실을 설명하는 데이터를 반환합니다. 그런 다음 명령 first
은 해당 목록의 첫 번째 항목을 잘라내는데, 그것은 거실에 대한 설명입니다. (우리가 생성한 변수 game-map
를 보면 거실을 설명하는 텍스트 조각이 거실에 대한 _모든_데이터를 포함한 목록에서 두 번째 항목이었습니다.)
이제 Lisp 프롬프트를 사용하여 함수를 테스트 해 봅시다.
'(this font and color)
학습서에서 다음 텍스트를 Lisp 프롬프트에 붙여 넣으십시오.
(describe-location 'living-room game-map)
user=> (describe-location 'living-room game-map)
(you are in the living-room of a wizard's house -
there is a wizard snoring loudly on the couch -)
Perfect! Just what we wanted... Notice how we put a quote in front of the symbol living-room
, since this symbol is just a piece of data naming the location (i.e. we want it read in Data Mode) , but how we didn't put a quote in front of the symbol game-map
, since in this case we want the list compiler to hunt down the data stored in the game-map
variable (i.e. we want the compiler to be in Code Mode and not just look at the word game-map
as a chunk of raw data)
완벽해요! 우리가 원하는 것이에요... 기호 living-room
앞에 인용 부호를 넣는 방법에 주목해보세요. 이 기호는 위치를 명명하는 데이터의 일부일 뿐입니다 (즉, 우리는 그게 데이터 모드에서 읽히기를 원합니다.). 인용 기호 앞에game-map
이 경우 우리는에 저장된 데이터 사냥하는 목록 컴파일러를 원하기 때문에,game-map
변수 (즉, 우리는 컴파일러에 있어야 할코드 모드그냥 단어 보지game-map
원시 데이터의 덩어리로)
# 함수형 프로그래밍 스타일 (The Functional Programming Style)
You may have noticed that our describe-location
function seems pretty awkward in several different ways. First of all, why are we passing in the variables for location and map as parameters, instead of just reading our global variables directly? The reason is that Lispers often like to write code in the Functional Programming Style (To be clear, this is completely unrelated in any way to the concept called "procedural programming" or "structural programming" that you might have learned about in high school...). In this style, the goal is to write functions that always follow the following rules:
describe-location
(설명-위치) 함수가 여러 가지 면에서 꽤 어색해 보인다는 것을 눈치채셨을 것입니다. 우선, 왜 전역 변수를 직접 읽지 않고 위치 및 지도 변수를 매개변수로 전달하고 있을까요? 그 이유는 Lisper는 함수형 프로그래밍 스타일로 코드를 작성하는 것을 좋아하기 때문입니다(분명히 말씀드리지만, 이것은 고등학교 때 배웠던 "절차적 프로그래밍" 또는 "구조적 프로그래밍"이라는 개념과는 전혀 관련이 없습니다...). 이 스타일에서는 항상 다음 규칙을 따르는 함수를 작성하는 것이 목표입니다:
- You only read variables that are passed into the function or are created by the function (So you don't read any global variables)
함수에 전달되거나 함수에 의해 생성된 변수만 읽습니다(전역 변수는 읽지 않음). - You never change the value of a variable that has already been set (So no incrementing variables or other such foolishness)
이미 설정된 변수의 값은 절대로 변경하지 않습니다(따라서 증분 변수나 기타 어리석은 짓은 하지 않습니다). - You never interact with the outside world, besides returning a result value. (So no writing to files, no writing messages for the user)
결과 값을 반환하는 것 외에는 외부와 상호 작용하지 않습니다. (따라서 파일 쓰기, 사용자 메시지 쓰기 금지)
You may be wondering if you can actually write any code like this that actually does anything useful, given these brutal restrictions... the answer is yes, once you get used to the style... Why would anyone bother following these rules? One very important reason: Writing code in this style gives your program referential transparency: This means that a given piece of code, called with the same parameters, always positively returns the same result and does exactly the same thing no matter when you call it - This can reduce programming errors and is believed to improve programmer productivity in many cases.
이런 잔인한 제한을 고려할 때 실제로 유용한 코드를 작성할 수 있는지 궁금할 수 있습니다... 스타일에 익숙해지면 대답은 '예'입니다. 왜 굳이 이런 규칙을 따라야 할까요? 한 가지 매우 중요한 이유가 있습니다: 이 스타일로 코드를 작성하면 프로그램에 참조 투명성을 제공합니다: 즉, 동일한 매개변수를 사용하여 호출된 특정 코드는 항상 동일한 결과를 반환하고 언제 호출하더라도 정확히 동일한 작업을 수행하므로 프로그래밍 오류를 줄일 수 있으며 많은 경우 프로그래머의 생산성을 향상시키는 것으로 알려져 있습니다.
Of course, you'll always have some functions that are not functional in style or you couldn't communicate with the user or other parts of the outside world. Most of the functions later in this tutorial do not follow these rules.
물론 스타일에 맞지 않거나 사용자 또는 외부 세계와 통신할 수 없는 함수가 항상 있을 수 있습니다. 이 튜토리얼 후반부에 나오는 대부분의 함수는 이러한 규칙을 따르지 않습니다.
Another problem with our describe-location
function is that it does not tell us about the paths in and out of the location to other locations. Let's write a function that describes these paths:
describe-location
함수의 또 다른 문제점은 해당 위치에서 다른 위치로 들어오고 나가는 경로에 대해 알려주지 않는다는 것입니다. 이러한 경로를 설명하는 함수를 작성해 보겠습니다:
(defn describe-path [path]
`(there is a ~(second path) going ~(first path) from here -))
Ok, now this function looks pretty strange: It almost looks more like a piece of data than a function. Let's try it out first and figures out how it does what it does later:
이제 이 함수가 꽤 이상해 보입니다: 함수라기보다는 데이터 조각처럼 보입니다. 먼저 사용해 보고 나중에 어떻게 작동하는지 알아봅시다:
(describe-path '(west door garden))
user=> (describe-path '(west door garden))
(user/there user/is user/a door user/going west user/from user/here clojure.core/-)
What is that !? The result is now cluttered with strange '/' characters and extra words ! This is because Clojure adds namespace information in to expressions that begin with a backquote. We won't go into detail here, but instead provide you with a way to remove that confusing output:
이게 뭐야! 이제 결과는 이상한 '/' 문자와 추가 단어로 어수선해집니다! 이는 클로저가 백따옴표로 시작하는 표현식에 네임스페이스 정보를 추가하기 때문입니다. 여기서는 자세히 설명하지 않고 대신 혼란스러운 출력을 제거하는 방법을 제공합니다:
(defn spel-print [list] (map (fn [x] (symbol (name x))) list))
and enter
그리고 입력합니다.
(spel-print (describe-path '(west door garden)))
user=> (spel-print (describe-path '(west door garden)))
(there is a door going west from here -)
Clojure namespaces are out of the scope of this tutorial. They are an important concept to Clojure, so we recommend to read about them in the language documentation.
클로저 네임스페이스는 이 튜토리얼의 범위를 벗어납니다. 네임스페이스는 클로저의 중요한 개념이므로 언어 설명서를 읽어보시기 바랍니다.
So now it's clear: This function takes a list describing a path (just like we have inside our game-map
variable) and makes a nice sentence out of it. Now when we look at the function again, we can see that the function "looks" a lot like the data it produces: It basically just splices the first and second item from the path into a declared sentence. How does it do this? It uses back-quoting !
이 함수는 게임 맵 변수 안에 있는 것처럼 경로를 설명하는 목록을 가져와서 멋진 문장을 만듭니다. 이제 함수를 다시 살펴보면 함수가 생성하는 데이터와 매우 유사하게 '보이는' 것을 알 수 있습니다: 기본적으로 경로의 첫 번째와 두 번째 항목을 선언된 문장으로 연결하기만 하면 됩니다. 어떻게 이렇게 할까요? 역따옴표를 사용합니다!
Remember that we've used a quote before to flip the compiler from Code Mode to Data Mode - Well, by using the back-quote (the quote in the upper left corner of the keyboard) we can not only flip, but then also flop back into Code Mode by using a tilde ("~") character:
이전에 따옴표를 사용하여 컴파일러를 코드 모드에서 데이터 모드로 전환한 것을 기억하실 텐데요, 백 따옴표(키보드 왼쪽 상단에 있는 따옴표)를 사용하면 전환할 수 있을 뿐만 아니라 물결표("~") 문자를 사용하여 다시 코드 모드로 전환할 수도 있습니다:
This "back-quoting" technique is a great feature in Lisp - it lets us write code that looks just like the data it creates. This happens frequently with code written in a functional style: By building functions that look like the data they create, we can make our code easier to understand and also build for longevity: As long as the data doesn't change, the functions will probably not need to be refactored or otherwise changed, since they mirror the data so closely. Imagine how you'd write a function like this in VB or C: You would probably chop the path into pieces, then append the text snippets and the pieces together again - A more haphazard process that "looks" totally different from the data that is created and probably less likely to have longevity.
이 "역 인용" 기법은 Lisp의 훌륭한 기능으로, 생성된 데이터와 똑같이 보이는 코드를 작성할 수 있게 해줍니다. 함수형 스타일로 작성된 코드에서 자주 발생합니다: 생성하는 데이터와 유사한 함수를 작성하면 코드를 더 쉽게 이해할 수 있고 오래 사용할 수 있습니다: 데이터가 변경되지 않는 한, 함수는 데이터를 매우 유사하게 반영하기 때문에 리팩터링하거나 다른 방식으로 변경할 필요가 없을 것입니다. VB나 C에서 이와 같은 함수를 작성한다고 상상해 보세요. 아마도 경로를 여러 조각으로 잘라낸 다음 텍스트 스니펫과 조각을 다시 추가할 것입니다. 생성된 데이터와 완전히 다른 '모양'을 가진 더 우연적인 프로세스로, 아마도 수명이 길지 않을 가능성이 높습니다.
Now we can describe a path, but a location in our game may have more than one path, so let's create a function called describe-path**s**
:
이제 경로를 설명할 수 있지만 게임 내 위치에는 둘 이상의 경로가 있을 수 있으므로 describe-paths라는 함수를 만들어 보겠습니다:
(defn describe-paths [location game-map]
(apply concat (map describe-path (rest (get game-map location)))))
This function uses another common functional programming technique: The use of Higher Order Functions - This means that the apply
and map
functions are taking other functions as parameters so that they can call them themselves - map
simply applies another function to every object in the list, basically causing all paths to be changed into pretty descriptions by the describe-path
function. The "apply concat" just cleans out some parentheses and isn't so important. Let's try this new function:
이 함수는 또 다른 일반적인 함수형 프로그래밍 기법을 사용합니다: 고차 함수 사용 - 즉, apply 함수와 map 함수가 다른 함수를 매개변수로 받아 스스로 호출할 수 있도록 하는 것입니다. map은 단순히 목록의 모든 객체에 다른 함수를 적용하여 기본적으로 모든 경로가 describe-path 함수에 의해 예쁜 설명으로 변경되도록 합니다. "apply concat"은 괄호 몇 개를 정리할 뿐이며 그다지 중요하지 않습니다. 이 새로운 함수를 사용해 보겠습니다:
(spel-print (describe-paths 'living-room game-map))
user=> (spel-print (describe-paths 'living-room game-map))
(there is a door going west from here -
there is a stairway going upstairs from here -)
Beautiful!
멋집니다!
We still have one thing we need to describe: If there are any objects on the floor at the location we are standing in, we'll want to describe them as well. Let's first write a helper function that tells us wether an item is in a given place:
아직 설명해야 할 것이 한 가지 더 있습니다: 우리가 서 있는 위치의 바닥에 어떤 물체가 있다면 그것도 설명해야 합니다. 먼저 특정 위치에 물건이 있는지 여부를 알려주는 도우미 함수를 작성해 봅시다:
(defn is-at? [obj loc obj-loc] (= (obj obj-loc) loc))
...the =
function tells us if the symbol from the object location list is the same as the current location.
...=
함수는 개체 위치 목록의 기호가 현재 위치와 동일한지 여부를 알려줍니다.
Let's try this out:
다음을 시도해봅시다:
(is-at? 'whiskey-bottle 'living-room object-locations)
user=> (is-at? 'whiskey-bottle 'living-room object-locations)
true
The symbol true
(or any value other than nil
) means that it's true that the whiskey-bottle is in living-room.
기호 true(또는 nil 이외의 값)는 위스키 병이 거실에 있다는 것이 사실임을 의미합니다.
Now let's use this function to describe the floor:
이제 이 함수를 사용하여 바닥을 설명해 보겠습니다:
(defn describe-floor [loc objs obj-loc]
(apply concat (map (fn [x]
`(you see a ~x on the floor -))
(filter (fn [x]
(is-at? x loc obj-loc)) objs))))
This function has a couple of new things: First of all, it has anonymous functions (fn
is the command for creating such a function.) That first fn
form is just the same as defining a helper function:
이 함수에는 몇 가지 새로운 기능이 있습니다: 우선 익명 함수가 있습니다(fn은 이러한 함수를 만드는 명령어입니다.) 첫 번째 fn 형식은 헬퍼 함수를 정의하는 것과 같습니다:
(defn blabla [x] `(you see a ~x on the floor.))
and then sending blabla
to the map function. The filter
function in this case is filtering out any objects from the list that are not at the current location before passing the list on to map to build pretty sentences. Let's try this new function:
를 호출한 다음 지도 함수에 블라블라를 보냅니다. 이 경우 필터 함수는 목록을 맵으로 전달하기 전에 목록에서 현재 위치에 없는 개체를 필터링하여 예쁜 문장을 만들 수 있습니다. 이 새로운 기능을 사용해 보겠습니다:
(spel-print (describe-floor 'living-room objects object-locations))
user=> (spel-print (describe-floor 'living-room objects object-locations))
(you see a whiskey-bottle on the floor - you see a bucket on the floor -)
Now we can tie all these descriptor functions into a single, easy command called look
that uses the global variables (therefore this function is not in the Functional Style) to feed all the descriptor functions and describes everything:
이제 이 모든 설명자 함수를 전역 변수(따라서 이 함수는 함수형 스타일에 속하지 않음)를 사용하여 모든 설명자 함수를 공급하고 모든 것을 설명하는 look이라는 하나의 쉬운 명령으로 묶을 수 있습니다:
(defn look []
(spel-print (concat (describe-location location game-map)
(describe-paths location game-map)
(describe-floor location objects object-locations))))
Let's try it:
이것을 해봐요.
user=> (look)
(you are in the living room of a wizards house -
there is a wizard snoring loudly on the couch -
there is a door going west from here -
there is a stairway going upstairs from here -
you see a whiskey-bottle on the floor -
you see a bucket on the floor -)
pretty cool!
꽤 좋은데!