LMU ☀️ CMSI 3801
LANGUAGES AND AUTOMATA I
HOMEWORK #3 PARTIAL ANSWERS

Code

TypeScript

import { open } from "node:fs/promises"

export function firstThenApply<T, U>(
  array: T[],
  predicate: (x: T) => boolean,
  consumer: (x: T) => U | undefined
): U | undefined {
  const first = array.find(predicate)
  if (first) {
    return consumer(first)
  }
  return undefined
}

export function* powersGenerator(base: bigint): Generator<bigint> {
  for (let power = 1n; true; power *= base) {
    yield power
  }
}

export async function meaningfulLineCount(filename: string): Promise<number> {
  let count = 0
  const file = await open(filename, "r")
  for await (const line of file.readLines()) {
    const trimmed = line.trim()
    if (trimmed && !trimmed.startsWith("#")) {
      count++
    }
  }
  return count
}

interface Sphere {
  kind: "Sphere"
  readonly radius: number
}

interface Box {
  kind: "Box"
  readonly width: number
  readonly length: number
  readonly depth: number
}

export type Shape = Sphere | Box

export function volume(shape: Shape): number {
  switch (shape.kind) {
    case "Sphere":
      return (4 / 3) * Math.PI * shape.radius ** 3
    case "Box":
      return shape.width * shape.length * shape.depth
  }
}

export function surfaceArea(shape: Shape): number {
  switch (shape.kind) {
    case "Sphere":
      return 4 * Math.PI * shape.radius ** 2
    case "Box":
      const { length, width, depth } = shape
      return 2 * (length * width + length * depth + width * depth)
  }
}

export interface BinarySearchTree<T> {
  size(): number
  insert(data: T): BinarySearchTree<T>
  contains(data: T): boolean
  inorder(): Iterable<T>
}

export class Empty<T> implements BinarySearchTree<T> {
  size(): number {
    return 0
  }
  insert(data: T): BinarySearchTree<T> {
    return new Node<T>(data, new Empty<T>(), new Empty<T>())
  }
  contains(data: T): boolean {
    return false
  }
  *inorder(): Iterable<T> {}
  toString(): string {
    return "()"
  }
}

class Node<T> implements BinarySearchTree<T> {
  constructor(
    private readonly data: T,
    private readonly left: BinarySearchTree<T>,
    private readonly right: BinarySearchTree<T>
  ) {}

  size(): number {
    return this.left.size() + this.right.size() + 1
  }

  insert(data: T): BinarySearchTree<T> {
    if (data < this.data) {
      return new Node(this.data, this.left.insert(data), this.right)
    } else if (data > this.data) {
      return new Node(this.data, this.left, this.right.insert(data))
    } else {
      return this
    }
  }

  contains(data: T): boolean {
    if (data < this.data) {
      return this.left.contains(data)
    } else if (data > this.data) {
      return this.right.contains(data)
    } else {
      return true
    }
  }

  *inorder(): Iterable<T> {
    yield* this.left.inorder()
    yield this.data
    yield* this.right.inorder()
  }

  toString(): string {
    const left = this.left instanceof Empty ? "" : this.left.toString()
    const right = this.right instanceof Empty ? "" : this.right.toString()
    return `(${left}${this.data}${right})`
  }
}

Haskell

module Exercises
    ( firstThenApply,
      powers,
      meaningfulLineCount,
      Shape(..),
      BST(Empty),
      volume,
      surfaceArea,
      size,
      contains,
      insert,
      inorder
    ) where

import qualified Data.Map as Map
import Data.List(isPrefixOf, find)
import Data.Char(isSpace)

firstThenApply :: [a] -> (a -> Bool) -> (a -> b) -> Maybe b
firstThenApply xs p f = f <$> find p xs

powers :: Integral a => a -> [a]
powers base = map (base^) [0..]

meaningfulLineCount :: FilePath -> IO Int
meaningfulLineCount filePath = do
    document <- readFile filePath
    let allWiteSpace = all isSpace
        trimStart = dropWhile isSpace
        isMeaningful line =
            not (allWiteSpace line) &&
            not ("#" `isPrefixOf` (trimStart line))
    return $ length $ filter isMeaningful $ lines document

data Shape
    = Sphere Double
    | Box Double Double Double
    deriving (Eq, Show)

volume :: Shape -> Double
volume (Sphere r) = (4 / 3) * pi * (r ^ 3)
volume (Box w l d) = w * l * d

surfaceArea :: Shape -> Double
surfaceArea (Sphere r) = 4 * pi * (r ^ 2)
surfaceArea (Box w l d) = 2 * ((w * l) + (w * d) + (l * d))

data BST a = Empty | Node a (BST a) (BST a)

insert :: Ord a => a -> BST a -> BST a
insert x Empty = Node x Empty Empty
insert x (Node y left right)
    | x < y = Node y (insert x left) right
    | x > y = Node y left (insert x right)
    | otherwise = Node y left right

contains :: Ord a => a -> BST a -> Bool
contains _ Empty = False
contains x (Node y left right)
    | x < y = contains x left
    | x > y = contains x right
    | otherwise = True

size :: BST a -> Int
size Empty = 0
size (Node _ left right) = 1 + size left + size right

inorder :: BST a -> [a]
inorder Empty = []
inorder (Node x left right) = inorder left ++ [x] ++ inorder right

instance (Show a) => Show (BST a) where
    show :: Show a => BST a -> String
    show Empty = "()"
    show (Node x Empty Empty) = "(" ++ show x ++ ")"
    show (Node x Empty right) = "(" ++ show x ++ show right ++ ")"
    show (Node x left Empty) = "(" ++ show left ++ show x ++ ")"
    show (Node x left right) = "(" ++ show left ++ show x ++ show right ++ ")"

Exercises

  1. Show how to constructively define the type of trees of elements of type $t$.

    Answer: A tree whose elements are all of type $t$ is either (1) empty or (2) a node consisting of a value of type $t$ and a list of subtrees, each of which is a tree of elements of type $t$.

    $$ \frac{}{\textsf{Empty}\!:\textsf{Tree}(t)} \quad\quad \frac{v\!:t \quad c\!:\textsf{Tree}(t)^*}{\textsf{Node}\:v\:c\!:\,\textsf{Tree}(t)} $$
  2. Give a definition by cases for the exponentiation of natural numbers.

    Answer: (This one is kind of tricky since the cases are done on the “second argument”):

    $\begin{array}{l} \textsf{exp}\!: \textsf{Nat} \rightarrow \textsf{Nat} \rightarrow \textsf{Nat} \\ \textsf{exp}\;m\;0 = 1 \\ \textsf{exp}\;m\;(\textsf{s}\,n) = \textsf{times}\;(\textsf{exp}\;m\;n)\;m \\ \end{array}$

  3. (a) Which are the inhabitants of $\textsf{Bool} + \textsf{Unit}$? (b) Which are the inhabitants of $\textsf{Bool} \mid \textsf{Unit}$?

    Answer: (Note how both types have three inhabitants each, but the inhabitants of the sum type are tagged):

    (a) These three: $\textsf{tag}_1\textsf{true}$, $\textsf{tag}_2\textsf{false}$, $\textsf{tag}_2()$
    (b) These three: $\textsf{true}$, $\textsf{false}$, $()$

  4. (a) Which are the inhabitants of $\textsf{Bool} \times \textsf{Unit}$? (b) Which are the inhabitants of $\textsf{Bool} \rightarrow \textsf{Unit}$?

    Answer:

    (a) These two: $(\textsf{true}, ())$ and $(\textsf{false}, ())$
    (b) There is only one: $\lambda x_{\textsf{Bool}}.()$
  5. What are the major arguments put forward in the article The String Type is Broken?

    Answer: It seems like there are two. First, many (most?) people do not understand basic concepts like encodings and normalization, and thus simple operations like reversal and uppercasing tend not to work the way these people expect them to. Second, different programming languages handle strings differently, so there tends to be no agreement for what a string even is.

  6. Can you give a type to $(\lambda x.(x\:x))(\lambda x.(x\:x))$? If so, what is it? If not, why not?

    Answer: Well, if you tried a naive approach, you would see that the first part, $(\lambda x.(x\:x))$, would have to be a function, so you would give that the type $t \to u$. But that function is applied to itself, so $t$ would be $t \to u$. This expansion never ends ($t \to t \to t \to \ldots$), so there’s no clean type for this expression. But think about what this expression is doing. Let’s “run” it:

    $(\lambda x.(x\:x))(\lambda x.(x\:x)) = (\lambda x.(x\:x))(\lambda x.(x\:x)) = (\lambda x.(x\:x))(\lambda x.(x\:x)) = \ldots$

    It never ends! So we can give it the type $\textsf{Never}$, which is used in programming languages for these situations. ($\textsf{Void}$ is also an acceptable answer.)

  7. Represent $x \not\in A$ in function notation.

    Answer: $\neg A\,x$

  8. What is a pure function? Why do we care about these things?

    Answer: A pure function is one without side effects, where the output depends only on the input and does not modify any external state, and gives the same output for each input no matter when and how many times it is called. We care because pure functions are thread-safe, easier to test, easier to prove correct, easier to parallelize, and often more amenable to optimization.

  9. How does Haskell isolate pure and impure code?

    Answer: Haskell has monadic types whose instances are descriptions of stateful and I/O operations—not the operations themselves. Haskell prevents impure operations to take place inside a pure function. The side effects are all run after main is evaluated.

  10. In TypeScript, which of | or & is closer to the idea of “subclassing” or “inheritance” from Python? Why? (An example will help!)

    Answer: Intersection (&) is closer. Here‘s a example, courtesy of ChatGPT:

    type Animal = { eat(): void }
    type Barker = { bark(): void }
    
    type Dog = Animal & Barker
    
    const d: Dog = {
      eat() { console.log("nom nom") },
      bark() { console.log("woof") },
    }