{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Function Calls in Lettuce\n", "\n", "Originally by Sriram Sankaranarayanan \n", "\n", "Modified by Ravi Mangal \n", "\n", "Last Modified: Mar 4, 2025.\n", "\n", "---\n", "\n", "Previously, we did not handle function calls in our fledgling Lettuce interpreter. Now we will add support for function calls. Before we do so, let us recall the full grammar.\n", "\n", "\n", "\n", "$$\\begin{array}{rcll}\n", "\\mathbf{Program} & \\rightarrow & TopLevel(\\mathbf{Expr}) \\\\[5pt]\n", "\\mathbf{Expr} & \\rightarrow & Const(\\mathbf{Number}) \\\\\n", " & | & True \\\\\n", " & | & False \\\\\n", " & | & Ident(\\mathbf{Identifier}) \\\\\n", " & | & Plus(\\mathbf{Expr}, \\mathbf{Expr}) \\\\\n", " & | & Minus(\\mathbf{Expr}, \\mathbf{Expr}) \\\\\n", " & | & Mult (\\mathbf{Expr}, \\mathbf{Expr}) \\\\\n", " & | & Div (\\mathbf{Expr}, \\mathbf{Expr}) \\\\\n", " & | & Log (\\mathbf{Expr}) \\\\\n", " & | & Exp (\\mathbf{Expr}) \\\\\n", " & | & Sine (\\mathbf{Expr}) \\\\\n", " & | & Cosine (\\mathbf{Expr}) \\\\\n", " & | & Geq (\\mathbf{Expr}, \\mathbf{Expr}) \\\\\n", " & | & Eq (\\mathbf{Expr}, \\mathbf{Expr}) \\\\\n", " & | & And ( \\mathbf{Expr}, \\mathbf{Expr} ) \\\\\n", " & | & Or ( \\mathbf{Expr}, \\mathbf{Expr} ) \\\\\n", " & | & Not ( \\mathbf{Expr}) \\\\\n", " & | & IfThenElse(\\mathbf{Expr}, \\mathbf{Expr}, \\mathbf{Expr}) & \\text{if (expr) then expr else expr} \\\\\n", " & | & Let( \\mathbf{Identifier}, \\mathbf{Expr}, \\mathbf{Expr}) & \\text{let identifier = expr in expr} \\\\\n", " & | & FunDef( \\mathbf{Identifier}, \\mathbf{Expr}) & \\text{function (identifier-formal-parameter) expr } \\\\ \n", " & | & FunCall(\\mathbf{Expr}, \\mathbf{Expr}) & \\text{function call - expr(expr)} \\\\[5pt]\n", "\\end{array}$$\n", "\n", "\n", "Here is the Scala definition again." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mtrait\u001b[39m \u001b[36mProgram\u001b[39m\n", "defined \u001b[32mtrait\u001b[39m \u001b[36mExpr\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mTopLevel\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mConst\u001b[39m\n", "defined \u001b[32mobject\u001b[39m \u001b[36mTrue\u001b[39m\n", "defined \u001b[32mobject\u001b[39m \u001b[36mFalse\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mIdent\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mPlus\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mMinus\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mMult\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mDiv\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mLog\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mExp\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mSine\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mCosine\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mGeq\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mEq\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mAnd\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mOr\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mNot\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mIfThenElse\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mLet\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mFunDef\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mFunCall\u001b[39m" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sealed trait Program\n", "sealed trait Expr\n", "\n", "case class TopLevel(e: Expr) extends Program\n", "\n", "case class Const(v: Double) extends Expr // Expr -> Const(v)\n", "case object True extends Expr // Expr -> True\n", "case object False extends Expr // Expr -> False\n", "case class Ident(s: String) extends Expr // Expr -> Ident(s)\n", "\n", "// Arithmetic Expressions\n", "case class Plus(e1: Expr, e2: Expr) extends Expr // Expr -> Plus(Expr, Expr)\n", "case class Minus(e1: Expr, e2: Expr) extends Expr // Expr -> Minus(Expr, Expr)\n", "case class Mult(e1: Expr, e2: Expr) extends Expr // Expr -> Mult (Expr, Expr)\n", "case class Div(e1: Expr, e2: Expr) extends Expr // Expr -> Mult(Expr, Expr)\n", "case class Log(e: Expr) extends Expr \n", "case class Exp(e: Expr) extends Expr\n", "case class Sine(e: Expr) extends Expr\n", "case class Cosine(e: Expr) extends Expr\n", "\n", "// Boolean Expressions\n", "case class Geq(e1: Expr, e2:Expr) extends Expr\n", "case class Eq(e1: Expr, e2: Expr) extends Expr\n", "case class And(e1: Expr, e2: Expr) extends Expr\n", "case class Or(e1: Expr, e2: Expr) extends Expr\n", "case class Not(e: Expr) extends Expr\n", "\n", "//If then else\n", "case class IfThenElse(e: Expr, eIf: Expr, eElse: Expr) extends Expr\n", "\n", "//Let bindings\n", "case class Let(s: String, defExpr: Expr, bodyExpr: Expr) extends Expr\n", "\n", "//Function definition\n", "case class FunDef(param: String, bodyExpr: Expr) extends Expr\n", "\n", "// Function call\n", "case class FunCall(funCalled: Expr, argExpr: Expr) extends Expr" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 1\n", "\n", "~~~\n", "let square = function(x)\n", " x * x\n", "in \n", " square(10) \n", "~~~\n", "\n", "This program evaluates to 100" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mp1\u001b[39m: \u001b[32mTopLevel\u001b[39m = TopLevel(Let(square,FunDef(x,Mult(Ident(x),Ident(x))),FunCall(Ident(square),Const(10.0))))" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val p1 = TopLevel( \n", " Let(\"square\", // let square = \n", " FunDef(\"x\", Mult(Ident(\"x\"), Ident(\"x\"))), // function (x) x * x\n", " FunCall(Ident(\"square\"), Const(10)) // in square(10)\n", " )\n", ")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 2\n", "~~~\n", "let x = 10 in \n", "let y = 15 in \n", "let sq1 = function (x)\n", " function (y) \n", " x + y * y\n", " in \n", " sq1(x)(y)\n", "~~~\n", " \n", "__Question:__ Map the different usages of x, y in the code above to the appropriate definitions?\n", "\n", "This program evaluates to $235$.\n" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mx\u001b[39m: \u001b[32mIdent\u001b[39m = \u001b[33mIdent\u001b[39m(\u001b[32m\"x\"\u001b[39m)\n", "\u001b[36my\u001b[39m: \u001b[32mIdent\u001b[39m = \u001b[33mIdent\u001b[39m(\u001b[32m\"y\"\u001b[39m)\n", "\u001b[36mfdef_inner\u001b[39m: \u001b[32mFunDef\u001b[39m = FunDef(y,Plus(Ident(x),Mult(Ident(y),Ident(y))))\n", "\u001b[36mfdef_outer\u001b[39m: \u001b[32mFunDef\u001b[39m = FunDef(x,FunDef(y,Plus(Ident(x),Mult(Ident(y),Ident(y)))))\n", "\u001b[36mcall_expr\u001b[39m: \u001b[32mFunCall\u001b[39m = FunCall(FunCall(Ident(sq1),Ident(x)),Ident(y))\n", "\u001b[36msq1_call\u001b[39m: \u001b[32mLet\u001b[39m = Let(sq1,FunDef(x,FunDef(y,Plus(Ident(x),Mult(Ident(y),Ident(y))))),FunCall(FunCall(Ident(sq1),Ident(x)),Ident(y)))\n", "\u001b[36mlety\u001b[39m: \u001b[32mLet\u001b[39m = Let(y,Const(15.0),Let(sq1,FunDef(x,FunDef(y,Plus(Ident(x),Mult(Ident(y),Ident(y))))),FunCall(FunCall(Ident(sq1),Ident(x)),Ident(y))))\n", "\u001b[36mletx\u001b[39m: \u001b[32mLet\u001b[39m = Let(x,Const(10.0),Let(y,Const(15.0),Let(sq1,FunDef(x,FunDef(y,Plus(Ident(x),Mult(Ident(y),Ident(y))))),FunCall(FunCall(Ident(sq1),Ident(x)),Ident(y)))))\n", "\u001b[36mp2\u001b[39m: \u001b[32mTopLevel\u001b[39m = TopLevel(Let(x,Const(10.0),Let(y,Const(15.0),Let(sq1,FunDef(x,FunDef(y,Plus(Ident(x),Mult(Ident(y),Ident(y))))),FunCall(FunCall(Ident(sq1),Ident(x)),Ident(y))))))" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val x = Ident(\"x\")\n", "val y = Ident(\"y\")\n", "val fdef_inner = FunDef(\"y\", Plus(x, Mult(y, y)))\n", "val fdef_outer = FunDef(\"x\", fdef_inner)\n", "val call_expr = FunCall(FunCall(Ident(\"sq1\"), x), y)\n", "val sq1_call = Let(\"sq1\", fdef_outer, call_expr)\n", "val lety = Let(\"y\", Const(15), sq1_call)\n", "val letx = Let(\"x\", Const(10), lety)\n", "val p2 = TopLevel(letx)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 3\n", "\n", "~~~\n", "let h = function(z)\n", " log(z) \n", "in \n", " let g = function(y) \n", " y/2.0 + h(y * 1.5)\n", " in \n", " let f = function (x) \n", " 1.0/x + g(x)\n", " in \n", " f(3.1415)\n", "~~~\n", "\n", "This should evaluate to 3.4392347..." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mx\u001b[39m: \u001b[32mIdent\u001b[39m = \u001b[33mIdent\u001b[39m(\u001b[32m\"x\"\u001b[39m)\n", "\u001b[36my\u001b[39m: \u001b[32mIdent\u001b[39m = \u001b[33mIdent\u001b[39m(\u001b[32m\"y\"\u001b[39m)\n", "\u001b[36mz\u001b[39m: \u001b[32mIdent\u001b[39m = \u001b[33mIdent\u001b[39m(\u001b[32m\"z\"\u001b[39m)\n", "\u001b[36mfDef\u001b[39m: \u001b[32mFunDef\u001b[39m = FunDef(x,Plus(Div(Const(1.0),Ident(x)),FunCall(Ident(g),Ident(x))))\n", "\u001b[36mgDef\u001b[39m: \u001b[32mFunDef\u001b[39m = FunDef(y,Plus(Div(Ident(y),Const(2.0)),FunCall(Ident(h),Mult(Ident(y),Const(1.5)))))\n", "\u001b[36mhDef\u001b[39m: \u001b[32mFunDef\u001b[39m = FunDef(z,Log(Ident(z)))\n", "\u001b[36mletf\u001b[39m: \u001b[32mLet\u001b[39m = Let(f,FunDef(x,Plus(Div(Const(1.0),Ident(x)),FunCall(Ident(g),Ident(x)))),FunCall(Ident(f),Const(3.1415)))\n", "\u001b[36mletg\u001b[39m: \u001b[32mLet\u001b[39m = Let(g,FunDef(y,Plus(Div(Ident(y),Const(2.0)),FunCall(Ident(h),Mult(Ident(y),Const(1.5))))),Let(f,FunDef(x,Plus(Div(Const(1.0),Ident(x)),FunCall(Ident(g),Ident(x)))),FunCall(Ident(f),Const(3.1415))))\n", "\u001b[36mleth\u001b[39m: \u001b[32mLet\u001b[39m = Let(h,FunDef(z,Log(Ident(z))),Let(g,FunDef(y,Plus(Div(Ident(y),Const(2.0)),FunCall(Ident(h),Mult(Ident(y),Const(1.5))))),Let(f,FunDef(x,Plus(Div(Const(1.0),Ident(x)),FunCall(Ident(g),Ident(x)))),FunCall(Ident(f),Const(3.1415)))))\n", "\u001b[36mp3\u001b[39m: \u001b[32mTopLevel\u001b[39m = TopLevel(Let(h,FunDef(z,Log(Ident(z))),Let(g,FunDef(y,Plus(Div(Ident(y),Const(2.0)),FunCall(Ident(h),Mult(Ident(y),Const(1.5))))),Let(f,FunDef(x,Plus(Div(Const(1.0),Ident(x)),FunCall(Ident(g),Ident(x)))),FunCall(Ident(f),Const(3.1415))))))" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val x = Ident(\"x\")\n", "val y = Ident(\"y\")\n", "val z = Ident(\"z\")\n", "\n", "val fDef = FunDef(\"x\", Plus(Div(Const(1.0), x), FunCall(Ident(\"g\"), x)) )\n", "val gDef = FunDef(\"y\", Plus(Div(y, Const(2.0)), FunCall(Ident(\"h\"), Mult(y, Const(1.5)))))\n", "val hDef = FunDef(\"z\", Log(z))\n", "\n", "val letf = Let(\"f\", fDef, FunCall(Ident(\"f\"), Const(3.1415)))\n", "val letg = Let(\"g\", gDef, letf)\n", "val leth = Let(\"h\", hDef, letg)\n", "\n", "val p3 = TopLevel(leth)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 4 (Bad)\n", "\n", "We can ask what happens if we define a recursive function like so:\n", "\n", "~~~\n", "let f = function (x) \n", " if (0 >= x) \n", " 1\n", " else\n", " (x - 1)* f(x - 1 )\n", "in \n", " f(10)\n", "~~~\n", "\n", "We can predict: note that the let expression \n", "\n", "~~~\n", "let f = exprA in expr B\n", "~~~\n", "\n", "we note that `f` is __not__ in scope for `exprA`. Therefore, when we make a recursive call `f(x-1)`, we will\n", "get an error, as we will see.\n", "\n", " We will modify this situation to allow recursive definitions coming next. But for now,\n", "this sort of a call will lead to an error.\n" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mx\u001b[39m: \u001b[32mIdent\u001b[39m = \u001b[33mIdent\u001b[39m(\u001b[32m\"x\"\u001b[39m)\n", "\u001b[36mcompX\u001b[39m: \u001b[32mGeq\u001b[39m = Geq(Const(0.0),Ident(x))\n", "\u001b[36mrecExpr\u001b[39m: \u001b[32mMult\u001b[39m = Mult(Minus(Ident(x),Const(1.0)),FunCall(Ident(f),Minus(Ident(x),Const(1.0))))\n", "\u001b[36mf_defn\u001b[39m: \u001b[32mFunDef\u001b[39m = FunDef(x,IfThenElse(Geq(Const(0.0),Ident(x)),Const(1.0),Mult(Minus(Ident(x),Const(1.0)),FunCall(Ident(f),Minus(Ident(x),Const(1.0))))))\n", "\u001b[36mletf\u001b[39m: \u001b[32mLet\u001b[39m = Let(f,FunDef(x,IfThenElse(Geq(Const(0.0),Ident(x)),Const(1.0),Mult(Minus(Ident(x),Const(1.0)),FunCall(Ident(f),Minus(Ident(x),Const(1.0)))))),FunCall(Ident(f),Const(10.0)))\n", "\u001b[36mp4\u001b[39m: \u001b[32mTopLevel\u001b[39m = TopLevel(Let(f,FunDef(x,IfThenElse(Geq(Const(0.0),Ident(x)),Const(1.0),Mult(Minus(Ident(x),Const(1.0)),FunCall(Ident(f),Minus(Ident(x),Const(1.0)))))),FunCall(Ident(f),Const(10.0))))" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val x = Ident(\"x\")\n", "val compX = Geq(Const(0), x)\n", "val recExpr = Mult(Minus(x, Const(1.0)), FunCall(Ident(\"f\"), Minus(x, Const(1.0))))\n", "val f_defn = FunDef(\"x\", IfThenElse(compX, Const(1.0), recExpr))\n", "val letf = Let(\"f\", f_defn, FunCall(Ident(\"f\"), Const(10.0)))\n", "val p4 = TopLevel(letf)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Scoping for Function Calls: Static vs. Dynamic\n", "\n", "It is important to understand how function calls capture scopes by considering a few examples both in Lettuce and Scala. \n", "\n", "### Example 1\n", "~~~\n", "let x = 10\n", "in \n", " let f = function(y)\n", " y * x\n", " in \n", " let x = 15\n", " in\n", " f(10)\n", "~~~\n", "\n", "Or equivalently in Scala\n", "\n", "~~~\n", "val x = 10\n", "val f = (y: Int) => (y * x) \n", "{\n", " val x = 15 \n", " f(10)\n", "}\n", "\n", "~~~\n", "\n", "In both cases, our code has a function `f` that multiplies its parameter `y` by `x`. But precisely which of the\n", "`x` should the function use?\n", "\n", " - `let x = 10` in the first line that is in scope when the function was first defined?, or \n", " - `let x = 15` in the third line that is in scope when the function is actually being called?\n", "\n", "\n", "What would the Scala compiler do?" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "f(10) = 100\n" ] }, { "data": { "text/plain": [ "\u001b[36mx\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m10\u001b[39m\n", "\u001b[36mf\u001b[39m: \u001b[32mInt\u001b[39m => \u001b[32mInt\u001b[39m = ammonite.$sess.cmd2$Helper$$Lambda$2169/512607238@21c3e734" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val x = 10 \n", "val f = (y: Int) => (y * x) \n", " {\n", " val x = 15 \n", " println(\"f(10) = \" + f(10))\n", " }\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can conclude that Scala uses the definition of `x` that was in scope __when the function was defined__.\n", "This kind of scoping is called __static scoping__. As opposed to dynamic scoping, in which case the value of `x` used is what is in scope when the function is called.\n", "\n", "### Why Static Scoping?\n", "\n", "Static scoping enables many useful programming idioms. We mention a few.\n", "\n", "#### (A) Partial application of functions\n", "\n", "Static scoping allows us to define functions some of whose arguments are already bound at definition time." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction\u001b[39m \u001b[36mmatchAgainstList\u001b[39m\n", "\u001b[36mrefList\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mInt\u001b[39m] = \u001b[33mList\u001b[39m(\u001b[32m1\u001b[39m, \u001b[32m2\u001b[39m, \u001b[32m3\u001b[39m, \u001b[32m4\u001b[39m, \u001b[32m5\u001b[39m, \u001b[32m6\u001b[39m, \u001b[32m7\u001b[39m, \u001b[32m8\u001b[39m, \u001b[32m9\u001b[39m, \u001b[32m10\u001b[39m)\n", "\u001b[36mboundMatchFun\u001b[39m: \u001b[32mList\u001b[39m[\u001b[32mInt\u001b[39m] => \u001b[32mBoolean\u001b[39m = ammonite.$sess.cmd7$Helper$$Lambda$2276/0x0000000100bed840@25aca7a1\n", "\u001b[36mres7_3\u001b[39m: \u001b[32mBoolean\u001b[39m = \u001b[32mtrue\u001b[39m" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// matchAgainstList takes in a reference_lst and returns a function\n", "// that itself takes in a list\n", "def matchAgainstList(reference_lst: List[Int]) (lstB: List[Int]) = {\n", " def belongsToRefList(x: Int) = {\n", " reference_lst.contains(x)\n", " }\n", " lstB.exists(belongsToRefList)\n", "}\n", "\n", "val refList = List(1,2, 3, 4, 5, 6, 7, 8, 9, 10)\n", "val boundMatchFun: (List[Int] => Boolean) = matchAgainstList (refList)\n", "{\n", " val refList = List(11, 12, 13, 14)\n", " boundMatchFun(List(5,6,7, 8))\n", "}\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### (B) Callbacks\n", "\n", "In the same vein as before, we can define callbacks to handle events using partially applied function to bind extra information into the callback function." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Keys Pressed so far are: Set(1, 2, -1, 10)\n" ] }, { "data": { "text/plain": [ "defined \u001b[32mclass\u001b[39m \u001b[36mEvent\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mKeyPress\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mMouseClick\u001b[39m\n", "defined \u001b[32mobject\u001b[39m \u001b[36mPrintStatistics\u001b[39m\n", "defined \u001b[32mfunction\u001b[39m \u001b[36mkeyPressEventHandlerFactory\u001b[39m\n", "defined \u001b[32mfunction\u001b[39m \u001b[36mhandleEventStream\u001b[39m" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "abstract class Event\n", "case class KeyPress(keyID: Int) extends Event\n", "case class MouseClick(x: Int, y: Int) extends Event\n", "case object PrintStatistics extends Event\n", "\n", "def keyPressEventHandlerFactory(): (Event => Unit) = {\n", " // Make a callback function and return it.\n", " var keysPressedSoFar: Set[Int] = Set() // Mutable state\n", " def keyPressEventCallBack(event: Event): Unit = {\n", " event match {\n", " case KeyPress(keyID) => { \n", " keysPressedSoFar = keysPressedSoFar ++ Set(keyID)\n", " }\n", " case PrintStatistics => {\n", " println(\"Keys Pressed so far are: \" + keysPressedSoFar)\n", " }\n", " case _ => ()\n", " }\n", " }\n", " keyPressEventCallBack\n", "}\n", "\n", "\n", "def handleEventStream() = {\n", " val eventsToTest = List[Event](KeyPress(1), KeyPress(2), MouseClick(10,20), KeyPress(10), KeyPress(-1), PrintStatistics)\n", " val keyPressEventCallBack = keyPressEventHandlerFactory()\n", " eventsToTest.foreach (keyPressEventCallBack)\n", "}\n", "\n", "\n", "handleEventStream()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Implementing Dynamic Scoping\n", "\n", "Turns out implementing dynamic scoping is conceptually easy. We could just ``inline'' the body of the function and substitute for the callee argument. \n", "\n", "~~~\n", "let x = 10 in \n", " let f = function(y) y * x in \n", " let x = 15 in \n", " f(10)\n", "~~~\n", "\n", "\n", "Everywhere `f` is called, simply replace it by `y * x` and substitute the argument of the call in place of `y`.\n", "If we did that, this would be the result and you get dynamic scoping.\n", "\n", "~~~\n", "let x = 10 in \n", " let f = function (y) y * x in \n", " let x = 15 in \n", " (10 * x)\n", "~~~\n", "\n", "#### Example 2\n", "\n", "~~~\n", "let x = 25 in\n", " let y = 15 in \n", " let x = x * y in \n", " let f = function (z) z * y + x in \n", " let x = y in \n", " f(10) + f(15)\n", "~~~\n", "\n", "Evaluate the program above under both static and dynamic scoping. Under dynamic scoping note that the program can be seen as equivalent to this program below:\n", "\n", "~~~\n", "let x = 25 in\n", " let y = 15 in \n", " let x = x * y in \n", " let f = function (z) z * y + x in \n", " let x = y in \n", " (10 * y + x ) + (15 * y + x)\n", "~~~\n", "\n", "\n", "Dynamic scoping is troublesome in many ways. It is hard for a programmer to reason about dynamic scoping since whoever implements a function f must be careful about the calling environment of the function which defines not just the formal arguments but also the other identifiers that the function's body may reference. \n", "\n", "Under dynamic scoping simple change of variable names can change the meaning of the function in unexpected ways.\n", "\n", "~~~\n", "let x = 25 in\n", " let y = 15 in \n", " let x = x * y in \n", " let f = function (z) z * y + x in \n", " let xNew = y in ## User changed name of x to xNew\n", " f(10) + f(15)\n", "~~~\n", "\n", "\n", "We will now explain how static scoping is implemented using _closures_.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Evaluating Lettuce with Function Calls\n", "\n", "We are now ready to start evaluating Lettuce with function calls.\n", "\n", "Thus far, we have values $\\mathbb{R}$ (for reals), $\\mathbb{B}$ (for booleans), $\\mathbf{error}$ (special error).\n", "Now we will augment it with a new type of value for functions that will be termed a _closure_.\n", "\n", "\n", "### Closure\n", "\n", "A closure is defined as a combination of two things: \n", "\n", "- A function definition `function(x) expr` where `x` is the formal parameter to the call and\n", "`expr` is the body of the function call.\n", "- An environment $\\sigma$ that will define all variables occuring freely in `expr` but not `x`.\n", "\n", "\n", "We will write a closure as \n", "\n", "$ \\text{Closure}( x, e, \\sigma)$ that represents a closure \n", "involving the function definition\n", "\n", "`function(x) e` with environment $\\sigma$.\n", "\n", "Let us see some examples of Closures.\n", "\n", "### Example 1\n", "\n", "Consider a function definition\n", "\n", "~~~\n", "function(y) x + y * y\n", "~~~\n", "\n", "`y` is the formal parameter but `x` is an identifier that is not a formal parameter of the function.\n", "We say that it occurs freely in the definition of the function.\n", "\n", "An example of a closure will be\n", "\n", "$\\text{Closure}( \\texttt{y},\\ \\underset{\\text{Func. Expr.}}{ \\underbrace{\\texttt{x + y * y }}}, \\underset{\\text{Env.}\\ \\sigma}{\\underbrace{\\{ x \\mapsto 10 \\}}} ) $.\n", "\n", "\n", "__Note:__ All that is missing is a value for the formal parameter `y`. Everything else is in place to evaluate the\n", "function. The moment `y` is available, we will have everything needed to execute the function body `x + y * y`.\n", "\n", "\n", "### Example 2\n", "\n", "Consider the function definition\n", "\n", "~~~\n", " function(x) function(y) x + y - 2 * z\n", "~~~\n", "\n", "`x` is the formal parameter of the function and the expression is `function (y) x + y - 2 * z `.\n", "Note that `z` occurs freely in the expression. The identifier `y` is not free since it is bound\n", "to the formal parameter of the inner function.\n", "\n", "A closure needs to specify a value for `z`. \n", "\n", "$\\text{Closure}(x,\\ (\\texttt{function(y) x + y - 2 * z}),\\ \\{ z \\mapsto 100 \\} )$\n", "\n", "\n", "## (Big-step) Operational Semantics of Functions\n", "\n", "Recall that $\\text{eval}(\\texttt{e}, \\sigma) = v$ states that evaluating the expression `e` under the\n", "environment $\\sigma$ yields the value $v$.\n", "\n", "Now our values can be of the form:\n", "- Real numbers: $\\mathbb{R}$,\n", "- Booleans: $\\mathbb{B}$,\n", "- Closures: $\\mathbb{C}$ of the form $\\text{Closure}(x, \\texttt{e}, \\sigma)$,\n", "- Error $\\mathbf{error}$.\n", "\n", "We will write a rule for handling function definitions for static scoping. \n", "Let us recall what is static scoping? It captures the environment at the time \n", "a function is defined. This is exactly what the closure does.\n", "\n", "$$ \\begin{array}{c}\n", "\\\\\n", "\\hline\n", "\\text{eval}(\\texttt{FuncDef}(x, e), \\sigma) = \\text{Closure}(x, \\texttt{e}, \\sigma) \\\\\n", "\\end{array} \\text{(func-def-ok)}$$\n", "\n", "All it remains is to specify what happens when we call a function.\n", "\n", "This is the really important rule to understand: the rest will be easy if this particular idea sinks in. \n", "So pay very careful attention:\n", "\n", "$$ \\begin{array}{c}\n", "\\text{eval}(\\texttt{funexpr}, \\sigma) = \\text{Closure}(\\textcolor{blue}{p}, \\textcolor{red}{\\texttt{e}}, \\textcolor{green}{\\pi}),\\ \\text{eval}(\\texttt{argexpr}, \\sigma) = v_2,\\ v_2 \\not= \\mathbf{error},\\ \\texttt{eval}( \\textcolor{red}{\\texttt{e}}, \\textcolor{green}{\\pi} [ \\textcolor{blue}{p} \\mapsto v_2 ] ) = v_3 \\\\\n", "\\hline\n", "\\texttt{eval}( \\texttt{FunCall(funexpr, argexpr)}, \\sigma ) = v_3 \\\\\n", "\\end{array} \\text{(funcall-ok)}\n", "$$\n", "\n", "The rule concerns itself with evaluating a function call `FunCall(funexpr, argexpr)`, i.e., a call of the form `funexpr(argexpr)` where\n", "- `funexpr` represents the function we are going to call.\n", "- `argexpr` represents the argument to the call.\n", "\n", "First, if `funexpr` is a function, then it better evaluate to a _closure_.\n", "- This is expressed by the condition $\\text{eval}(\\texttt{funexpr}, \\sigma) = \\text{Closure}(\\color{blue}{p}, \\color{red}{\\texttt{e}}, \\color{green}{\\pi})$ on the top of the bar. This closure has a formal param $p$, \n", "body $\\texttt{e}$ and the environment that was saved at function define time is $\\pi$.\n", "\n", "Next, we evaluate the argument to the funciton call `argexpr`. Let this evaluate to $v_2$ where $v_2$ is not an __error__ value.\n", "\n", "Finally, we are ready to call the closure. Note that with the arrival of $v_2$ from previous step, we have\n", "everything needed to evaluate a function call. So we can extend the environment $\\pi$ by binding \n", "$p$ the formal parameter to $v_2$. With that done, everything is ready to evaluate the body of the closure\n", "$\\texttt{e}$.\n", "\n", "We need to add error rules. If the function expression is not a closure:\n", "$$ \\begin{array}{c}\n", "\\text{eval}(\\texttt{funexpr}, \\sigma) \\not \\in \\mathbb{C}\\\\\n", "\\hline\n", "\\texttt{eval}( \\texttt{FunCall(funexpr, argexpr)}, \\sigma ) = \\mathbf{error}\\\\\n", "\\end{array} \\text{(funcall-nok-1)}\n", "$$\n", "\n", "If the argument to the call leads to error:\n", "$$ \\begin{array}{c}\n", "\\text{eval}(\\texttt{funexpr}, \\sigma) = \\text{Closure}(\\color{blue}{x}, \\color{red}{\\texttt{e}}, \\color{green}{\\pi}),\\ \\text{eval}(\\texttt{argexpr}, \\sigma) = \\mathbf{error} \\\\\n", "\\hline\n", "\\texttt{eval}( \\texttt{FunCall(funexpr, argexpr)}, \\sigma ) = \\mathbf{error}\\\\\n", "\\end{array} \\text{(funcall-nok-2)}\n", "$$\n", "\n" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mtrait\u001b[39m \u001b[36mValue\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mNumValue\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mBoolValue\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mClosure\u001b[39m\n", "defined \u001b[32mobject\u001b[39m \u001b[36mErrorValue\u001b[39m\n", "defined \u001b[32mfunction\u001b[39m \u001b[36mvalueToNumber\u001b[39m\n", "defined \u001b[32mfunction\u001b[39m \u001b[36mvalueToBoolean\u001b[39m\n", "defined \u001b[32mfunction\u001b[39m \u001b[36mvalueToClosure\u001b[39m" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "/* 1. Define the values */\n", "sealed trait Value \n", "case class NumValue(d: Double) extends Value\n", "case class BoolValue(b: Boolean) extends Value\n", "/* -- Let us add Closure to the set of values --*/\n", "case class Closure(x: String, e: Expr, pi: Map[String, Value]) extends Value\n", "case object ErrorValue extends Value\n", "\n", "\n", "/*2. Operators on values */\n", "\n", "def valueToNumber(v: Value): Double = v match {\n", " case NumValue(d) => d\n", " case _ => throw new IllegalArgumentException(s\"Error: Asking me to convert Value: $v to a number\")\n", "}\n", "\n", "def valueToBoolean(v: Value): Boolean = v match {\n", " case BoolValue(b) => b\n", " case _ => throw new IllegalArgumentException(s\"Error: Asking me to convert Value: $v to a boolean\")\n", "}\n", "\n", "def valueToClosure(v: Value): Closure = v match {\n", " case Closure(x, e, pi) => Closure(x, e, pi)\n", " case _ => throw new IllegalArgumentException(s\"Error: Asking me to convert Value: $v to a closure\")\n", "}\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mfunction\u001b[39m \u001b[36mevalExpr\u001b[39m\n", "defined \u001b[32mfunction\u001b[39m \u001b[36mevalProgram\u001b[39m" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def evalExpr(e: Expr, env: Map[String, Value]): Value = {\n", " \n", " /* Method to deal with binary arithmetic operations */\n", " \n", " def applyArith2 (e1: Expr, e2: Expr) (fun: (Double , Double) => Double) = {\n", " val v1 = valueToNumber(evalExpr(e1, env))\n", " val v2 = valueToNumber(evalExpr(e2, env))\n", " val v3 = fun(v1, v2)\n", " NumValue(v3)\n", " } /* -- We have deliberately curried the method --*/\n", " \n", " /* Helper method to deal with unary arithmetic */\n", " def applyArith1(e: Expr) (fun: Double => Double) = {\n", " val v = valueToNumber(evalExpr(e, env))\n", " val v1 = fun(v)\n", " NumValue(v1)\n", " }\n", " \n", " /* Helper method to deal with comparison operators */\n", " def applyComp(e1: Expr, e2: Expr) (fun: (Double, Double) => Boolean) = {\n", " val v1 = valueToNumber(evalExpr(e1, env))\n", " val v2 = valueToNumber(evalExpr(e2, env))\n", " val v3 = fun(v1, v2)\n", " BoolValue(v3)\n", " }\n", " \n", " \n", " e match {\n", " case Const(f) => NumValue(f)\n", " \n", " case Ident(x) => {\n", " if (env contains x) \n", " env(x)\n", " else \n", " throw new IllegalArgumentException(s\"Undefined identifier $x\")\n", " }\n", " \n", " case True => BoolValue(true)\n", " \n", " case False => BoolValue(false)\n", " \n", " case Plus(e1, e2) => applyArith2 (e1, e2) ( _ + _ )\n", " \n", " case Minus(e1, e2) => applyArith2(e1, e2) ( _ - _ )\n", " \n", " case Mult(e1, e2) => applyArith2(e1, e2) (_ * _)\n", " \n", " case Div(e1, e2) => applyArith2(e1, e2) {\n", " case (_, 0.0) => throw new IllegalArgumentException(s\"Divide by zero error in divisor ${e2}\")\n", " case (v1, v2) => v1/v2\n", " }\n", " \n", " case Log(e1) => applyArith1(e1) {\n", " case v if v > 0.0 => math.log(v)\n", " case v => throw new IllegalArgumentException(s\"Log of a negative number ${e} evaluates to ${v}!\")\n", " }\n", " \n", " case Exp(e1) => applyArith1(e1)(math.exp)\n", " \n", " case Sine(e1) => applyArith1(e1)(math.sin)\n", " \n", " case Cosine(e1) => applyArith1(e1)(math.cos)\n", " \n", " case Geq(e1, e2) => applyComp(e1, e2)(_ >= _)\n", " \n", " case Eq(e1, e2) => applyComp(e1, e2)(_ == _)\n", " \n", " case And(e1, e2) => { /* Short circuit eval of And */\n", " val v1 = evalExpr(e1, env)\n", " v1 match {\n", " case BoolValue(false) => BoolValue(false)\n", " case BoolValue(true) => {\n", " val v2 = evalExpr(e2, env)\n", " v2 match {\n", " case BoolValue(_) => v2\n", " case _ => throw new IllegalArgumentException(\n", " s\"And of a boolean and non-boolean expr: ${e2} which evaluated to ${v2}\") \n", " }\n", " }\n", " case _ => throw new IllegalArgumentException(s\"And of a non-boolean expr: ${e1} which evaluted to ${v1}\")\n", " }\n", " }\n", " \n", " case Or(e1, e2) => { /* Short circuit eval of OR*/\n", " val v1 = evalExpr(e1, env)\n", " v1 match {\n", " case BoolValue(true) => BoolValue(true)\n", " case BoolValue(false) => {\n", " val v2 = evalExpr(e2, env)\n", " v2 match {\n", " case BoolValue(_) => v2\n", " case _ => throw new IllegalArgumentException(\n", " s\"Or of a boolean and non-boolean expr: ${e2} which evaluated to ${v2}\") \n", " }\n", " }\n", " case _ => throw new IllegalArgumentException(s\"Or of a non-boolean expr: ${e1} which evaluted to ${v1}\")\n", " }\n", " }\n", " \n", " case Not(e1) => {\n", " val v = evalExpr(e1, env)\n", " v match {\n", " case BoolValue(b) => BoolValue(!b)\n", " case _ => throw new IllegalArgumentException(s\"Not of a non-boolean expr: ${e} which evaluated to ${v}\")\n", " }\n", " }\n", " \n", " case IfThenElse(e1, e2, e3) => {\n", " val v = evalExpr(e1, env)\n", " v match {\n", " case BoolValue(true) => evalExpr(e2, env)\n", " case BoolValue(false) => evalExpr(e3, env)\n", " case _ => throw new IllegalArgumentException(s\"If-then-else condition expr: ${e1} is non-boolean -- evaluates to ${v}\")\n", " }\n", " }\n", " \n", " case Let(x, e1, e2) => {\n", " val v1 = evalExpr(e1, env) // eval e1\n", " val env2 = env + (x -> v1) // create a new extended env\n", " evalExpr(e2, env2) // eval e2 under that.\n", " }\n", " \n", " case FunDef(x, e) => {\n", " Closure(x, e, env) // Return a closure with the current enviroment.\n", " }\n", " \n", " case FunCall(e1, e2) => {\n", " val v1 = evalExpr(e1, env)\n", " val v2 = evalExpr(e2, env)\n", " // Since evaluating e2 did not fail with an exception,\n", " // if I reach this point in my execution, I know that\n", " // neither v1 nor v2 are error.\n", " v1 match {\n", " case Closure(x, closure_ex, closed_env) => {\n", " // First extend closed_env by binding x to v2\n", " val new_env = closed_env + ( x -> v2)\n", " // Evaluate the body of the closure under the extended environment.\n", " evalExpr(closure_ex, new_env)\n", " }\n", " case _ => throw new IllegalArgumentException(s\"Function call error: expression $e1 does not evaluate to a closure\")\n", " }\n", " }\n", " }\n", "}\n", "\n", "def evalProgram(p: Program) = {\n", " val m: Map[String, Value] = Map[String,Value]()\n", " p match { \n", " case TopLevel(e) => evalExpr(e, m)\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mv1\u001b[39m: \u001b[32mValue\u001b[39m = NumValue(100.0)" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val v1 = evalProgram(p1)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mv2\u001b[39m: \u001b[32mValue\u001b[39m = NumValue(235.0)" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val v2 = evalProgram(p2)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mv3\u001b[39m: \u001b[32mValue\u001b[39m = NumValue(3.4392347752010837)" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val v3 = evalProgram(p3)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "ename": "", "evalue": "", "output_type": "error", "traceback": [ "\u001b[31mjava.lang.IllegalArgumentException: Undefined identifier f\u001b[39m", " $sess.cmd6Wrapper$Helper.evalExpr(\u001b[32mcmd6.sc\u001b[39m:\u001b[32m35\u001b[39m)", " $sess.cmd6Wrapper$Helper.evalExpr(\u001b[32mcmd6.sc\u001b[39m:\u001b[32m128\u001b[39m)", " $sess.cmd6Wrapper$Helper.applyArith2$1(\u001b[32mcmd6.sc\u001b[39m:\u001b[32m7\u001b[39m)", " $sess.cmd6Wrapper$Helper.evalExpr(\u001b[32mcmd6.sc\u001b[39m:\u001b[32m46\u001b[39m)", " $sess.cmd6Wrapper$Helper.evalProgram(\u001b[32mcmd6.sc\u001b[39m:\u001b[32m146\u001b[39m)", " $sess.cmd10Wrapper$Helper.(\u001b[32mcmd10.sc\u001b[39m:\u001b[32m1\u001b[39m)", " $sess.cmd10Wrapper.(\u001b[32mcmd10.sc\u001b[39m:\u001b[32m251\u001b[39m)", " $sess.cmd10$.(\u001b[32mcmd10.sc\u001b[39m:\u001b[32m148\u001b[39m)", " $sess.cmd10$.(\u001b[32mcmd10.sc\u001b[39m:\u001b[32m-1\u001b[39m)" ] } ], "source": [ "val v4 = evalProgram(p4) // Will say that f is undefined identifier since we have let f = ... f ... in ... pattern" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mself\u001b[39m: \u001b[32mIdent\u001b[39m = \u001b[33mIdent\u001b[39m(\u001b[32m\"self\"\u001b[39m)\n", "\u001b[36mn\u001b[39m: \u001b[32mIdent\u001b[39m = \u001b[33mIdent\u001b[39m(\u001b[32m\"n\"\u001b[39m)\n", "\u001b[36me1\u001b[39m: \u001b[32mMult\u001b[39m = Mult(Ident(n),FunCall(FunCall(Ident(self),Ident(self)),Minus(Ident(n),Const(1.0))))\n", "\u001b[36me2\u001b[39m: \u001b[32mIfThenElse\u001b[39m = IfThenElse(Eq(Ident(n),Const(0.0)),Const(1.0),Mult(Ident(n),FunCall(FunCall(Ident(self),Ident(self)),Minus(Ident(n),Const(1.0)))))\n", "\u001b[36me3\u001b[39m: \u001b[32mFunDef\u001b[39m = FunDef(n,IfThenElse(Eq(Ident(n),Const(0.0)),Const(1.0),Mult(Ident(n),FunCall(FunCall(Ident(self),Ident(self)),Minus(Ident(n),Const(1.0))))))\n", "\u001b[36me4\u001b[39m: \u001b[32mFunDef\u001b[39m = FunDef(self,FunDef(n,IfThenElse(Eq(Ident(n),Const(0.0)),Const(1.0),Mult(Ident(n),FunCall(FunCall(Ident(self),Ident(self)),Minus(Ident(n),Const(1.0)))))))\n", "\u001b[36me5\u001b[39m: \u001b[32mLet\u001b[39m = Let(pfact,FunDef(self,FunDef(n,IfThenElse(Eq(Ident(n),Const(0.0)),Const(1.0),Mult(Ident(n),FunCall(FunCall(Ident(self),Ident(self)),Minus(Ident(n),Const(1.0))))))),FunCall(FunCall(Ident(pfact),Ident(pfact)),Const(10.0)))\n", "\u001b[36mprog\u001b[39m: \u001b[32mTopLevel\u001b[39m = TopLevel(Let(pfact,FunDef(self,FunDef(n,IfThenElse(Eq(Ident(n),Const(0.0)),Const(1.0),Mult(Ident(n),FunCall(FunCall(Ident(self),Ident(self)),Minus(Ident(n),Const(1.0))))))),FunCall(FunCall(Ident(pfact),Ident(pfact)),Const(10.0))))" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "/*-- AN IMPLEMENTATION OF A RECURSIVE FUNCTION\n", "let pfact = function (self) \n", " function (n)\n", " if (n == 0)\n", " then 1\n", " else \n", " n * ( self (self) (n-1) )\n", " in\n", " (pfact (pfact)) (10)\n", " --*/\n", "\n", "val self = Ident(\"self\")\n", "val n = Ident(\"n\")\n", "val e1 = Mult(n, FunCall( FunCall(self, self), Minus(n, Const(1))))\n", "val e2 = IfThenElse(Eq(n, Const(0)), Const(1), e1)\n", "val e3 = FunDef(\"n\", e2)\n", "val e4 = FunDef(\"self\", e3)\n", "val e5 = Let(\"pfact\", e4, FunCall(FunCall(Ident(\"pfact\"), Ident(\"pfact\")), Const(10)))\n", "\n", "val prog = TopLevel(e5)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[36mres18\u001b[39m: \u001b[32mValue\u001b[39m = NumValue(3628800.0)" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "evalProgram(prog)" ] } ], "metadata": { "kernelspec": { "display_name": "Scala", "language": "scala", "name": "scala" }, "language_info": { "codemirror_mode": "text/x-scala", "file_extension": ".sc", "mimetype": "text/x-scala", "name": "scala", "nbconvert_exporter": "script", "version": "2.13.14" } }, "nbformat": 4, "nbformat_minor": 4 }