• No results found

Augustsson and Carlsson [AC99] demonstrated how dependent types are used in the creation of an interpreter for the Typedλ-Calculus [Bar92]. Here, Augustsson

and Carlsson modelled the calculus as an EDSL within Cayenne, a dependently typed language [Aug98] of the time. The resulting interpreter is well-typed as it allows for static compile guarantees over the types of expressions. This section takes the Arith language from earlier in the chapter and constructs a well-typed interpreter for it within Idris, together with a description of how the formalisms are implemented.

4.6.1

Language Definition

Figure 4.1 presented the abstract syntax for Arith. Within functional languages the expressions for Arith can be encoded as an inductive ADT. Listing 4.2 presents one such definition.

However, there are problems with this definition when it comes to implementing the typing rules for the language. The types for an EDSL can be modelled within Idris using an enumerated type2. For example, the set of typesTin Arith can be encoded in Idris as follows:

data ArithTy = TyNum | TyBool

In non-dependently typed functional languages typing rules can be modelled through pattern matching, and the creation of an interpretation function that evaluates the expressions according to their expected types. For example, using the language defin- ition given in Listing 4.2, a partial description of an evaluation function using non-

2More complex types can be defined, but for the purposes of this explanation a simple enumerated

type is sufficient.

4.6. Well-Typed Interpreters dependently typed expressions can be described as follows. First, a data type to collect

evaluation results is defined:

data EvalRes = Err String | BRes Bool | IRes Int

Evaluation will result either in an error if the example is ill-typed, a boolean value for evaluating boolean expressions, or an integer for evaluating integer expressions. However, to aid in creation of the evaluation function,EvalRescan be turned into a Functorand then turned into an Applicative data structure. Both functors and

applicatives are functional programming techniques that provide for more abstract and generalised programming. This tutorial is now starting to rely upon the need of more complicated programming language techniques to demonstrate show to implement the typing rules.

An alternative approach to implementing the typing rules is to still encapsulate the result inEvalRes, but use patterns and pattern matching to pass the expressions

around. Listing 4.3 presents such an implementation of theevalfunction.

Within Listing 4.3,evalNumandevalBoolare used to implement the evaluation and error handling for dealing with numerical and boolean expressions respectively, and also wherenegatenegates arithmetic expressions. Only a naïve definition for

evalBoolis given, as well as helper functionsdoAndanddoOrthat implement con-

junction and disjunction of boolean terms.

However, this method of implemention for the evalutor for Arith is not the best approach. First, it is rather verbose and requires the creation of custom data type to handle errors and report successes. Second, it requires the creation of functions for each expression, and also the creation of the interpreter. And third, it facilitates the construction of ill-typed expressions that are only detected at run-(née)-evaluation time.

Recall, that dependent types are types that can depend on values. With dependent types, a better implementation of the typing rules involves parameterising the ADT that represents expressions further by the enumerated type representing types in the language—see Listing 4.4. What follows is a direct embedding of the typing rules

directly within the types of the expressions.

With this representation and construction of the language as an EDSL within Idris, the ability to detect ill-typed expressions now becomes a compile time check.

4. Well-Typed (Abstract) Interpreters

1 eval : Arith -> EvalRes

2 eval e@(Num x) = evalNum e

3 eval e@( Bool x) = evalBool e

4

5 eval e@(Not x) = evalBool e

6 eval e@(And x y) = evalBool e

7 eval e@(Or x y) = evalBool e

8

9 eval e@(Add x y) = evalNum e

10 eval e@(Sub x y) = evalNum e

11 eval e@(Mul x y) = evalNum e

12 eval e@(Div x y) = evalNum e

13 eval e@(Neg x) = negate x

14

15 evalBool : Arith -> EvalRes

16 evalBool ( Boolean b) = BRes b

17 evalBool (And x y) = doAnd ( evalBool x) ( evalBool y)

18 evalBool (Or x y) = doOr ( evalBool x) ( evalBool y)

19 evalBool (Not x) = not x

20 evalBool _ = Err "NaB"

21

22 doAnd : EvalRes -> EvalRes -> EvalRes

23 doAnd ( BRes x) ( BRes y) = BRes $ x && y

24 doAnd _ _ = Err "Not BoolExpr "

25

26 doOr : EvalRes -> EvalRes -> EvalRes

27 doOr ( BRes x) ( BRes y) = BRes $ x || y

28 doOr _ _ = Err "Not BoolExpr "

Listing 4.3: Naïve implementation of an evaluation function forArith.

1 data Arith : ArithTy -> Type where

2 Num : Int -> Arith TyNum

3 Boolean : Bool -> Arith TyBool

4

5 Neg : Arith TyNum -> Arith TyNum

6 Add : Arith TyNum -> Arith TyNum -> Arith TyNum

7 Sub : Arith TyNum -> Arith TyNum -> Arith TyNum

8 Div : Arith TyNum -> Arith TyNum -> Arith TyNum

9 Mul : Arith TyNum -> Arith TyNum -> Arith TyNum

10

11 And : Arith TyBool -> Arith TyBool -> Arith TyBool

12 Or : Arith TyBool -> Arith TyBool -> Arith TyBool

13 Not : Arith TyBool -> Arith TyBool

Listing 4.4: Example of using Dependent Types to embed and model typing rules for boolean and integer arithmetic expressions directly in the ADT representing language expressions.

4.6. Well-Typed Interpreters Moreover, less code is required to ensure that expressions are well typed. This guaran-

teescorrectness-by-constructionof language expressions.

The Arith language does not have variables, and as such the typing environment for Idris can be borrowed completely for modelling the language. If we were to provide variables, then being able to track the types of variables is essential. For more informa- tion how to achieve this in dependently typed languages readers are asked to consult for more information: Augustsson and Carlsson [AC99], Brady and Hammond [BH12] and The Idris Community [Idr15].

Remark. An alternative means to model this simple typed Arithmetic language is to introduce functions types in the type to describe operations. This will allow for a more compact and stronger definition. The implementation of the Well-Typed Inter- preter in The Idris Community [Idr15], demonstrates this technique.

4.6.2

Implementing The Interpreter

With the implementation of the Arith language complete, the remainder of this sec- tion discusses how to implement the interpreter. Evaluating expressions from Arith will result in a value that is either a boolean or integer type. An interesting question when constructing the interpreter is:How to return a value of the correct type?When

exploring earlier how to implement typing rules this was modelled using an ADT. Within dependently typed languages, types can be computed using a function that returns the correct type.

1 interpTy : ArithTy -> Type

2 interpTy TyNum = Int

3 interpTy TyBool = Boolean

Listing 4.5: A Type Interpreter for theArithlanguage.

Within Idris the interpretation semantics defined in Figure 4.2 can be represented as a function that when given a value returns the appropriate type. This function,

interpTyis detailed in Listing 4.5. Each type in the model is interpreted directly to its

Idris equivalent. This function will be used in the definition of the interpreter to ensure that the result returned has the correct type. Further, notice the similarity between the definition ofinterpTyand the formalism given in Figure 4.2.

4. Well-Typed (Abstract) Interpreters

The focus now turns to expression evaluation, and how the expressions in Arith are transformed into their Idris equivalents and simultaneously evaluated. This inter- pretation was presented in Figure 4.3. Listing 4.6 presents a possible implementation in Idris.

As with the implementation of the type interpreter, note the similarities between the formal representation, and implementation.

1 interp : Arith t -> interpTy t

2 interp (Num x) = x

3 interp ( Boolean x) = x

4 interp (Neg x) = ( -1) * ( interp x)

5 interp (Not x) = not ( interp x)

6 interp (Add x y) = ( interp x) + ( interp y)

7 interp (Sub x y) = ( interp x) + ( interp y)

8 interp (Div x y) = ( interp x) `div ` ( interp y)

9 interp (Mul x y) = ( interp x) * ( interp y)

10 interp (And x y) = ( interp x) || ( interp y)

11 interp (Or x y) = ( interp x) && ( interp y)

Listing 4.6: Implementation of the evaluation semantics for Arith in Idris A well-typed interpreter for the Arith language has now been constructed. De- pendent types allows for these constructs to be implemented efficiently and concisely, Generally speaking, Idris itself has good support for defining more advanced EDSLs and with concepts not treated here—see Brady and Hammond [BH12]. However, the correctness of the evaluation function has not yet been guaranteed. As with the implementation of list concatenation presented in §4.5.2, there is a mistake in the implementation. Lines 6 & 7 that details the evaluation of addition and subtraction expressions for integer numbers has been evaluated using addition in both cases. Line 7 should subtract the interpretation of xfromy. The next section details an approach that can be used to avoid such mistakes.