Finish part 1 of the homework discussion.

This commit is contained in:
Danila Fedorin 2020-10-27 17:34:20 -07:00
parent 1626c6d535
commit 781a15627c

65
HW4.md
View File

@ -1,4 +1,5 @@
# Benjamin's Solution
# Part 1: Solution Discussion
## Benjamin's Solution
Looks like Ben opted to literally translate the `Prog` nonterminal into Haskell. This is perfectly valid: if anything, it's a more faithful translation of the syntax. However, as you probably know, the definition is isomorphic to a list:
```Haskell
@ -30,10 +31,10 @@ I appreciated the following comment in the code:
What about a closed form approach? Something like `(b-a+1)(b+a)/2`. If `*` and `/` were O(1) operations, this would make the whole summation O(1).
# Owen's Solution
## Owen's Solution
Looks like Owen had some trouble with this homework assignment. From what I can tell, the code should not compile; there are a few things here that can be improved.
## Allowing `Expr` to be `Int`
### Allowing `Expr` to be `Int`
I'm looking at this line of code:
```Haskell
@ -50,7 +51,7 @@ data Expr = Lit Int
Unlike the previous version, this will create a constructor called `Lit` with type `Int -> Expr`. _This_ time, the `Int` refers to the type `Int`, and so, `Lit 3` will represent an expression that contains the number `3`.
## Defining `Reg`
### Defining `Reg`
Haskell's `type` is a way to create a _type alias_. So, if you have a type you use a lot (perhaps `Maybe String`), you
can give it another name:
@ -64,7 +65,7 @@ Then, `Maybe String` and `MyType` mean the same thing. For defining `Reg`, you w
data Reg = A | B | C
```
## Using `undefined` or `_holes`
### Using `undefined` or `_holes`
It's hard to get the whole program compiling in one go. Haskell supports placeholders, which can be used anywhere, and will compile (mostly) no matter what. Of course, they'll either crash at runtime (`undefined`) or not _really_ compile (`_holes`). However, if you have something like:
```Haskell
@ -86,10 +87,10 @@ foldr _help 1 [1,2,3]
```
This will almost compile, except that at the very end, Haskell will tell you the type of the expression you need to fill in for `_help`. It will also suggest what you can put for `_help`!
## Parenthesization
### Parenthesization
This is a small nitpick. Instead of writing `RegStore A (x)`, you would write `RegStore A x`. In this case, you probably wanted `RegStore A (Lit x)` (you do need parentheses here!).
## Making Type Errors Impossible
### Making Type Errors Impossible
I'm looking at this part of Part 2:
```Haskell
@ -120,7 +121,7 @@ data Stmt = RegStore Reg IntExpr
Hope all this helps!
# Jaspal's Solution
## Jaspal's Solution
Notably different in Jaspal's solution from the others in this group is that they used strings for registers instead of a data type definition. I think this was allowed by this homework assignment; however, I think that using a data type is better for our purposes.
The issue is, if your register is a string, it can be _any_ string. You can have register `"A"`, register `"a"`, register `"Hello World"`, and so on. But our language only has three registers: `A`, `B`, and `R`. If we define a data type as follows:
@ -142,7 +143,7 @@ less bulky than they otherwise would be.
It doesn't look like the code compiles; here are a few things you can do to bring it closer to a working state.
## Type Names and Constructor Names
### Type Names and Constructor Names
Type names (`Expr`, `Int`, `Bool`) all _have to_ start with an uppercase letter. Thus, you should change the following line:
```Haskell
@ -176,10 +177,54 @@ data Stmt
| Break
```
## String Notation
### String Notation
I have made a case that you _shouldn't_ be using strings for this homework. However, you'll probably use them in the future. So, I want to point out: `'A'` does _not_ mean the string "A". It means a single _character_, like `'A'` in C. To create a String in Haskell, you want to use double quotes: `"A"`.
Other than those two things, it looks like this should work!
# My Solution
My solution to Part 1 is pretty much identical to everyone else's; I opted to use `Prog = [Stmt]` instead of defining a data type, but other than that, not much is different.
I had some fun in Part 2, though. In particular, instead of creating two separate data types, `BExpr` and `IExpr`, I used a Generalized Algebraic Data Type (GADT). Thus, my code was as follows:
```Haskell
data Expr a where
Lit :: Int -> Expr Int
Reg :: Reg -> Expr Int
Plus :: Expr Int -> Expr Int -> Expr Int
Leq :: Expr Int -> Expr Int -> Expr Bool
Not :: Expr Bool -> Expr Bool
```
This defines an `Expr` type that's parameterized by a type `a`. I defined `a` to be "the type the expression evaluates to". So,
* `Expr Int` is an expression that is guaranteed to produce an integer
* `Expr Bool` is an expression that is guaranteed to produce a boolean.
Unlike regular ADTs, you can restrict how one can create instances of `Expr a`. Thus, I can make it so that it's only possible to create `Expr Int` using `Lit` and `Reg`; the constructors `Plus`, `Leq`, and `Not` produce `Expr Bool`.
Why in the world would you use this? The benefits aren't immediately obvious in this language; however, suppose you had a ternary expression like `a ? b : c` in C or C++. Using our `BExpr` and `IExpr` we'd have to add constructors to _both_:
```Haskell
data IExpr
= -- ...
| ITern BExpr IExpr IExpr
| -- ...
data BExpr
= -- ...
| BTern BExpr BExpr BExpr
| -- ...
```
However, using the GADT definition, you should just be able to write:
```
data Expr a where
-- ...
Tern :: Expr Bool -> Expr b -> Expr b -> Expr b
```
Which covers both `x ? 1 : 2` and `x ? true : false`, but doesn't allow `x ? true : 3` (which would be a type error). Using a GADT, the "bexpr" part of the abstract syntax maps to `Expr Bool`, and the "iexpr" part maps to `Expr Int`.
I also wrote a quick interpreter using the Monad Transformer Library. Monads are one of the classic "what the heck is this" things in Haskell; someone once said there are more monad tutorials than Haskell programmers out there. I don't think I'm qualified to give a good explanation, but in short: I used a _state monad_ (which is basically a Haskell type that helps you keep track of changing variables) and an _exception monad_ (which implements exception handling similar to Java's `throw`) to implement the language. Every `Loop` was implicitly a `catch` clause, and `Break` was a `throw`. Thus, when you reached a break, you would immediately return to the innermost loop, and continue from there. Combined with a GADT for expressions, the evaluator turned out _really_ compact: less than 25 lines of actual code!