-
[프언] #05. Expressions2023-2학기/프로그래밍언어 2023. 9. 25. 21:39
이번 글에서는 이때까지 배웠던 지식을 바탕으로 프로그래밍 언어 하나를 설계해보도록 하겠습니다.
Values & Environment
Scala와 유사하게 프로그램을 Expression으로 정의할 것인데요. 편의를 위해 저희가 만드는 프로그래밍 언어에는 딱 2가지 Value, Integer와 Boolean만 존재한다고 생각합시다.
$$v\in \textbf{Val} = \mathbb{Z}+ \mathit{Bool}$$
이제 Value를 정의했으니 이에 대한 연산에 대해 한번 생각해보겠습니다. 예를 들어 x에 10을 더해 그 값을 y에 저장한다고 생각을 해 봅시다. 이 때, 계산한 값을 y에 저장하려면 y는 그 값이 바뀔 수 있는 Variable이여야 하며 이 Variable은 Pre-assigned, 이전에 정의되어 있어야 합니다.
이렇게 Value에 저장된 값을 Variable로 옮겨져있는 이 상황을 "Environment"라고 합니다.
$$\rho \in \textbf{Env}=\textbf{Var} \to \textbf{Val}$$
예를 들어, 아래와 같은 C++ 프로그램이 있다고 가정합시다.
int x = 10 // ...
이런 C++ 프로그램에서, 10이라는 값이 x에 할당되어 있습니다. 이걸 다르게 말하면 ...이라고 되어 있는 부분에 작성한 코드를 작성하게 되면, 이 코드는 x=10인 Environment 위에 놓여있는 것이고, 이 Envirionment 위에서 x를 호출했을 경우, 그 x는 10이라는 값을 가지는 것입니다.
저희는 Environment를 표현하기 위해 아래와 같은 표현을 사용할 겁니다.
- $[\; ]$ : 빈 Environment를 나타냅니다.
- $[ x\mapsto v ]\rho $ : $x$의 값이 $v$의 값에 의존함, 다른 말로 $v$의 값이 $x$에 할당되어 있음을 의미합니다. $\rho$는 이 Environment의 이름입니다.
Expression을 표현하기 위해서는 아래와 같은 표현을 사용합니다.
$$ \rho \vdash e\Rightarrow v$$
$\rho$는 주어진 Environment를, $e$는 Expression을, $v$는 Expression을 계산한 값을 의미합니다. 예를 들어, $ [] \vdash 1+1\Rightarrow 2$ 입니다. 위 식에서 $1+1$을 계산하기 위해 요구되는 Environment가 없기 때문에, $\rho$ 자리에 빈 Environment를 넣었고 Expression인 $1+1$을 계산하면 $2$가 됩니다.
그러면 $ [] \vdash x+1\Rightarrow ?$를 계산하면 어떻게 될까요? Expression $x+1$을 계산하기 위해서는 $x$의 값이 필요합니다. 따라서, 위에 주어진 조건만으로 화살표 오른쪽에 들어갈 값을 구할 수는 없습니다. $x$에 값을 할당해주는 Environment가 필요하죠. $ [x\mapsto 1]] \vdash x+1\Rightarrow 2$ 이렇게요.
Evaluation Rules
방금까지 Expression과 Environment를 기술하는 방법에 대해 알아보았습니다. 이제 위 방법들과 Inference Rule을 같이 이용하면, 저희가 사용할 언어의 Syntax에 대한 설명을 보다 쉽게 할 수 있습니다.
Evaluation Rule #1 위는 Axiom입니다. $n$이라는 Expression을 계산한 값은 항상 $n$임을 의미합니다. 이렇게 Inference Rule을 이용해 Syntax에 대해 기술한 것을 우리는 Evaluation Rule이라 부를 것입니다.
Evaluataion Rule #2, #3 위 두 Expression Rule은 더하기 기호와 빼기 기호가 어떻게 동작하는지를 나타냅니다. Evaluataion Rule #2는 Expression $E_1$을 계산한 값이 $n_1$이고, $E_2$를 계산한 값이 $n_2$라면 $E_1+E_2$를 계산하면 $n_1+n_2$가 됨을 의미합니다. Evaluation Rule #3은 뺄셈에 대해 같은 정의를 의미합니다.
Evaluation Rule #4, #5 위 두 Expression Rule은 $iszero$가 무엇을 의미하는지를 나타냅니다. $E$의 값이 0일 경우는 $true$가 되고, 0이 아닐 경우에는 $false$가 됩니다.
Evaluation Rule #6, #7 위 두 Expression Rule은 $if\ then\ else$가 어떻게 계산되는지를 나타냅니다. 자세한 설명은 생략하겠습니다.
Evaluation Rule #8 $let$ Expression에 대해서는 설명을 조금 더 하고 넘어가겠습니다. $let\ E_1 \ in \ E_2$는 새로운 변수 $x$를 만들고 그 값을Expression $E_1$로 지정해준다고 생각하시면 됩니다.
Evaluation Rule #9 위 Expression Rule은 괄호가 무엇을 의미하는지를 나타냅니다. 자세한 설명은 생략하겠습니다.
이러한 Expression Rule을 사용하면 아무리 복잡한 Expression들이라도 세부적이고 단순한 형태의 Expression으로 분리하여 그 값을 계산할 수 있습니다. 그 예시를 한번 보겠습니다.
Environment가 $[i\mapsto 1, v\mapsto 5, x\mapsto 10]$로 주어졌을 때, Expression $\left( x-3 \right) - \left( v-i \right)$를 계산하는 과정은 아래와 같이 작성할 수 있습니다.
Envirionment가 $[x\mapsto 33, y\mapsto 22]$로 주어졌을 때, Expression $if \ iszero \ \left( x-11\right)\ then \ y-2 \ else\ y-4$를 계산하는 과정은 아래와 같이 작성할 수 있습니다.
빈 Environment에서 Expression $let\ x=5\ in\ x-3$을 계산하는 과정은 아래와 같이 작성할 수 있습니다.
이렇게 $let$ Expression을 사용할 때마다 새로운 변수가 만들어지고, 이 변수의 값은 주어진 Environment에 의존한다고 생각하면 됩니다. 만약 여러개의 변수를 만들어야 할 상황이라면, $let$ Expression 안에 $let$ Expression을 넣어서 사용할 수 있습니다.
let z = 5 in let x = 3 in let y = x - 1 in let x = 4 in x - (x - y)
이렇게요. 위를 계산한 값은 3입니다.
언어의 작성
이제 지금까지 저희가 설계했던 Language를 한번 Scala에서 구현해보도록 하겠습니다. 첫 단계는 글 제일 처음에 했던 것 처럼 Environment와 변수들을 정의하는 것입니다.
type Env = HashMap[Var, Val] sealed trait Val case class IntVal(n: Int) extends Val case class BoolVal(b: Boolean) extends Val
그 다음으로는 Program 그 자체를 정의할 것입니다. Program은 여러 Expression의 모임이고, Program 그 자체도 Expression이라고 볼 수 있으므로, 아래와 같이 정의할 수 있습니다.
sealed trait Program sealed trait Expr extends Program
이제 상수, 변수, 더하기, 빼기, $Iszero$, $If$, $Let$, 괄호 등 여러가지 Expression을 하나하나 추가해줍니다.
sealed trait Program sealed trait Expr extends Program case class Const(n: Int) extends Expr case class Var(s: String) extends Expr case class Add(l: Expr, r: Expr) extends Expr case class Sub(l: Expr, r: Expr) extends Expr case class Iszero(c: Expr) extends Expr case class Ite(c: Expr, t: Expr, f Expr) extends Expr case class Let(name: Var, value: Expr, body: Expr) extends Expr case class Paren(expr: Expr) extends Expr
이제 이렇게 Expression과 Environment를 전부 정의했으므로, 텍스트를 받아서 위의 스칼라 코드가 알아들을 수 있는 표현으로 바꿔주는 프로그램이 필요합니다. 예컨데,
let x = 7 in let y = 2 in x - 7
이런 텍스트를 받아서, 아래와 같은 스칼라 코드로 바꿔주는 프로그램이죠.
Let(Var(x), Const(7), Let(Var(y), Const(2), Sub(Var(x), Var(y))))
이런 프로그램을 우리는 Parser라고 부릅니다. 하지만, 이 글에서 Parser에 대한 내용은 다루지 않고 누군가 이미 만들어둔 아주 멋진 Parser가 있다고 가정하고 설명을 계속해보도록 하겠습니다.
다음으로 작성해야 할 코드는 바로 Environment와 Expression을 입력받아 이 Expression을 계산하는 함수입니다.
def eval(env: Env, expr: Expr): Val = expr match { case Const(n) => IntVal(n) case Var(s) => if (env.exists((a: Var, Val)) => a._1 == Var(s)) env(Var(s)) else throw new Exception("1") case Add(a, b) => (eval(env, a), eval(env, b)) match { case (x: IntVal, y: IntVal) => IntVal(x.n + y.n) case _ => throw new Exception("Type Error") } case Sub(a, b) => (eval(env, a), eval(env, b)) match { case (x: IntVal, y: IntVal) => IntVal(x.n = y.n) case _ => throw new Exception("Type Error") } case IsZero(c) => eval(env, c) match { case (x: Intval) => BoolVal(x.n == 0) case _ => BoolVal(false) } case Ite(c, t, f) => eval(env, c) match { case v: BoolVal => if (v.b) eval(env, t) else eval(env, f) case _ => throw new Excpetion("Type Error") } case Let(name, value, body) => { val new env = env + (name -> eval(env, value)) eval(new_env, body) } case Paren(expr: Expr) => eval(env, Expr) }
코드에 대한 자세한 설명은 생략하도록 하겠습니다.
감사합니다.
이 글은 컴퓨터공학과 학부생이 개인 공부 목적으로 작성한 글이므로, 부정확하거나 틀린 내용이 있을 수 있으니 참고용으로만 봐주시면 좋겠습니다. 레퍼런스 및 글에 대한 기본적인 정보는 이 글을 참고해 주세요.
'2023-2학기 > 프로그래밍언어' 카테고리의 다른 글
[프언] #07. Lexical Scoping of Variables (0) 2023.10.17 [프언] #06. Procedures (0) 2023.10.16 [프언] #04. Functional Programming (2) 2023.09.24 [프언] #03. Inductive Definitions (0) 2023.09.09 [프언] #02. Programming in Scala (3) 2023.09.09