Functional Data Structures
Tessa Kelly
engineer at NoRedInk
@t_kelly9
Binary Tree API
module BinaryTree
exposing
( BinaryTree
, new
, empty
, member
, insert
, remove
)
Array
Dict
Tree I & II
Array Based
Strategy:
- Use Array as our base
- Derive child indices
- Hope for a complete binary tree
type alias BinaryTree comparable =
Array (Maybe comparable)
leftChild : Int -> Int
leftChild index =
2 * index + 1
rightChild : Int -> Int
rightChild index =
2 * index + 2
empty : BinaryTree comparable
empty =
Array.empty
new : comparable -> BinaryTree comparable
new value =
Array.initialize 1 (\_ -> Just value)
member : comparable -> BinaryTree comparable -> Bool
member =
memberAt 0
memberAt : Int -> comparable -> BinaryTree comparable -> Bool
memberAt index value tree =
case Array.get index tree of
Just (Just nodeValue) ->
if value < nodeValue then
memberAt (leftChild index) value tree
else if value > nodeValue then
memberAt (rightChild index) value tree
else
value == nodeValue
_ ->
False
insert : comparable -> BinaryTree comparable -> BinaryTree comparable
insert =
insertAt 0
insertAt : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
insertAt index value tree =
case Array.get index tree of
Just (Just nodeValue) ->
if value < nodeValue then
insertAt (leftChild index) value tree
else if value > nodeValue then
insertAt (rightChild index) value tree
else
tree
Just Nothing ->
Array.set index (Just value) tree
Nothing ->
fillWithEmptiesUntil index value tree
fillWithEmptiesUntil : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
fillWithEmptiesUntil index value tree =
Array.repeat (index - Array.length tree) Nothing
|> Array.push (Just value)
|> Array.append tree
remove : comparable -> BinaryTree comparable -> BinaryTree comparable
remove =
removeAt 0
removeAt : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
removeAt index value tree =
case Array.get index tree of
Just (Just nodeValue) ->
if value < nodeValue then
removeAt (leftChild index) value tree
else if value > nodeValue then
removeAt (rightChild index) value tree
else
case ( Array.get (leftChild index) tree, Array.get (rightChild index) tree ) of
( Just (Just leftValue), _ ) ->
Array.set index (Just leftValue) (removeAt (leftChild index) leftValue tree)
( _, Just (Just rightValue) ) ->
Array.set index (Just rightValue) (removeAt (rightChild index) rightValue tree)
( _, _ ) ->
Array.set index Nothing tree
_ ->
tree
Shape of a Node
type alias BinaryTree comparable =
Array (Maybe comparable)
Creating a Binary Tree
type alias BinaryTree comparable =
Array (Maybe comparable)
empty : BinaryTree comparable
empty =
Array.empty
new : comparable -> BinaryTree comparable
new value =
Array.initialize 1 (\_ -> Just value)
Checking for Values
member : comparable -> BinaryTree comparable -> Bool
member =
memberAt 0
memberAt : Int -> comparable -> BinaryTree comparable -> Bool
memberAt index value tree =
case Array.get index tree of
Just (Just nodeValue) ->
if value < nodeValue then
memberAt (leftChild index) value tree
else if value > nodeValue then
memberAt (rightChild index) value tree
else
value == nodeValue
_ ->
False
Inserting Values
insert : comparable -> BinaryTree comparable -> BinaryTree comparable
insert =
insertAt 0
insertAt : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
insertAt index value tree =
case Array.get index tree of
Just (Just nodeValue) ->
if value < nodeValue then
insertAt (leftChild index) value tree
else if value > nodeValue then
insertAt (rightChild index) value tree
else
tree
Just Nothing ->
Array.set index (Just value) tree
Nothing ->
.... uh oh!!!
insert : comparable -> BinaryTree comparable -> BinaryTree comparable
insert =
insertAt 0
insertAt : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
insertAt index value tree =
case Array.get index tree of
Just (Just nodeValue) ->
if value < nodeValue then
insertAt (leftChild index) value tree
else if value > nodeValue then
insertAt (rightChild index) value tree
else
tree
Just Nothing ->
Array.set index (Just value) tree
Nothing ->
fillWithEmptiesUntil index value tree
fillWithEmptiesUntil : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
fillWithEmptiesUntil index value tree =
Array.repeat (index - Array.length tree) Nothing
|> Array.push (Just value)
|> Array.append tree
remove : comparable -> BinaryTree comparable -> BinaryTree comparable
remove =
removeAt 0
removeAt : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
removeAt index value tree =
case Array.get index tree of
Just (Just nodeValue) ->
if value < nodeValue then
removeAt (leftChild index) value tree
else if value > nodeValue then
removeAt (rightChild index) value tree
else
case ( Array.get (leftChild index) tree, Array.get (rightChild index) tree ) of
( Just (Just leftValue), _ ) ->
Array.set index (Just leftValue) (removeAt (leftChild index) leftValue tree)
( _, Just (Just rightValue) ) ->
Array.set index (Just rightValue) (removeAt (rightChild index) rightValue tree)
( _, _ ) ->
Array.set index Nothing tree
_ ->
tree
Removing Values
Eek!
Types, halp!
Adding a Node type
This helps make our code more readable by cutting out the bananas `Just Nothing` business, but it doesn't solve all of our problems with this implementation.
type alias BinaryTree comparable =
Array (Node comparable)
type Node comparable
= Node comparable
| Empty
insertAt : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
insertAt index value tree =
case Array.get index tree of
Just (Node nodeValue) ->
if value < nodeValue then
insertAt (leftChild index) value tree
else if value > nodeValue then
insertAt (rightChild index) value tree
else
tree
Just Empty ->
Array.set index (Node value) tree
Nothing ->
fillWithEmptiesUntil index value tree
removeAt : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
removeAt index value tree =
case Array.get index tree of
Just (Node nodeValue) ->
if value < nodeValue then
removeAt (leftChild index) value tree
else if value > nodeValue then
removeAt (rightChild index) value tree
else
case ( Array.get (leftChild index) tree, Array.get (rightChild index) tree ) of
( Just (Node leftValue), _ ) ->
Array.set index (Node leftValue) (removeAt (leftChild index) leftValue tree)
( _, Just (Node rightValue) ) ->
Array.set index (Node rightValue) (removeAt (rightChild index) rightValue tree)
( _, _ ) ->
Array.set index Empty tree
_ ->
tree
Dict Based
Strategy:
- Use Dict as our base
- Derive child indices
- Continue using helpers to our advantage
type alias BinaryTree comparable =
Dict Int comparable
empty : BinaryTree comparable
empty =
Dict.empty
member : comparable -> BinaryTree comparable -> Bool
member =
memberAt 0
memberAt : Int -> comparable -> BinaryTree comparable -> Bool
memberAt index value tree =
case Dict.get index tree of
Just nodeValue ->
if value < nodeValue then
memberAt (leftChild index) value tree
else if value > nodeValue then
memberAt (rightChild index) value tree
else
value == nodeValue
_ ->
False
insert : comparable -> BinaryTree comparable -> BinaryTree comparable
insert =
insertAt 0
insertAt : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
insertAt index value tree =
case Dict.get index tree of
Just nodeValue ->
if value < nodeValue then
insertAt (leftChild index) value tree
else if value > nodeValue then
insertAt (rightChild index) value tree
else
tree
Nothing ->
Dict.insert index value tree
remove : comparable -> BinaryTree comparable -> BinaryTree comparable
remove =
removeAt 0
removeAt : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
removeAt index value tree =
case Dict.get index tree of
Just nodeValue ->
if value < nodeValue then
removeAt (leftChild index) value tree
else if value > nodeValue then
removeAt (rightChild index) value tree
else
case ( Dict.get (leftChild index) tree, Dict.get (rightChild index) tree ) of
( Just leftValue, _ ) ->
Dict.insert index leftValue (removeAt (leftChild index) leftValue tree)
( _, Just rightValue ) ->
Dict.insert index rightValue (removeAt (rightChild index) rightValue tree)
( Nothing, Nothing ) ->
Dict.remove index tree
_ ->
tree
leftChild : Int -> Int
leftChild index =
2 * index + 1
rightChild : Int -> Int
rightChild index =
2 * index + 2
new : comparable -> BinaryTree comparable
new value =
Dict.singleton 0 value
Creating a Binary Tree
type alias BinaryTree comparable =
Dict Int comparable
empty : BinaryTree comparable
empty =
Dict.empty
new : comparable -> BinaryTree comparable
new value =
Dict.singleton 0 value
member : comparable -> BinaryTree comparable -> Bool
member =
memberAt 0
Checking for Values
member : comparable -> BinaryTree comparable -> Bool
member =
memberAt 0
memberAt : Int -> comparable -> BinaryTree comparable -> Bool
memberAt index value tree =
case Dict.get index tree of
Just nodeValue ->
if value < nodeValue then
memberAt (leftChild index) value tree
else if value > nodeValue then
memberAt (rightChild index) value tree
else
value == nodeValue
_ ->
False
Inserting Values
insert : comparable -> BinaryTree comparable -> BinaryTree comparable
insert =
insertAt 0
insertAt : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
insertAt index value tree =
case Dict.get index tree of
Just nodeValue ->
if value < nodeValue then
insertAt (leftChild index) value tree
else if value > nodeValue then
insertAt (rightChild index) value tree
else
tree
Nothing ->
Dict.insert index value tree
Removing Values
remove : comparable -> BinaryTree comparable -> BinaryTree comparable
remove =
removeAt 0
removeAt : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
removeAt index value tree =
case Dict.get index tree of
Just nodeValue ->
if value < nodeValue then
removeAt (leftChild index) value tree
else if value > nodeValue then
removeAt (rightChild index) value tree
else
case ( Dict.get (leftChild index) tree, Dict.get (rightChild index) tree ) of
( Just leftValue, _ ) ->
Dict.insert index leftValue (removeAt (leftChild index) leftValue tree)
( _, Just rightValue ) ->
Dict.insert index rightValue (removeAt (rightChild index) rightValue tree)
( Nothing, Nothing ) ->
Dict.remove index tree
_ ->
tree
Tree Based, I
Strategy:
- Use "pointers"
- Pretend records have entirely analogous uses to JavaScript Objects
- Hope that the types stay nice
type BinaryTree comparable
= Node
{ value : comparable
, left : BinaryTree comparable
, right : BinaryTree comparable
}
| Empty
empty : BinaryTree comparable
empty =
Empty
new : comparable -> BinaryTree comparable
new value =
Node
{ value = value
, left = Empty
, right = Empty
}
member : comparable -> BinaryTree comparable -> Bool
member value tree =
case tree of
Node node ->
if value < node.value then
member value node.left
else if value > node.value then
member value node.right
else
value == node.value
Empty ->
False
insert : comparable -> BinaryTree comparable -> BinaryTree comparable
insert value tree =
case tree of
Node node ->
if value < node.value then
Node
{ value = node.value
, left = insert value node.left
, right = node.right
}
else if value > node.value then
Node
{ value = node.value
, left = node.left
, right = insert value node.right
}
else
Node node
Empty ->
new value
remove : comparable -> BinaryTree comparable -> BinaryTree comparable
remove value tree =
case tree of
Node node ->
if value < node.value then
Node
{ value = node.value
, left = remove value node.left
, right = node.right
}
else if value > node.value then
Node
{ value = node.value
, left = node.left
, right = remove value node.right
}
else
case ( node.left, node.right ) of
-- No children to consider
( Empty, Empty ) ->
empty
-- One child, on the right
( Empty, Node rightTree ) ->
Node rightTree
-- One child, on the left
( Node leftTree, Empty ) ->
Node leftTree
-- Two children, right and left
( Node leftTree, Node rightTree ) ->
Node
{ value = rightTree.value
, left = Node leftTree
, right = remove rightTree.value (Node rightTree)
}
Empty ->
Empty
type BinaryTree comparable
= Node
{ value : comparable
, left : BinaryTree comparable
, right : BinaryTree comparable
}
| Empty
Shape of a Node
type BinaryTree comparable
= Node
{ value : comparable
, left : BinaryTree comparable
, right : BinaryTree comparable
}
| Empty
empty : BinaryTree comparable
empty =
Empty
new : comparable -> BinaryTree comparable
new value =
Node
{ value = value
, left = Empty
, right = Empty
}
Creating a Binary Tree
insert : comparable -> BinaryTree comparable -> BinaryTree comparable
insert value tree =
case tree of
Node node ->
if value < node.value then
Node
{ value = node.value
, left = insert value node.left
, right = node.right
}
else if value > node.value then
Node
{ value = node.value
, left = node.left
, right = insert value node.right
}
else
Node node
Empty ->
new value
Inserting Values
remove : comparable -> BinaryTree comparable -> BinaryTree comparable
remove value tree =
case tree of
Node node ->
if value < node.value then
Node
{ value = node.value
, left = remove value node.left
, right = node.right
}
else if value > node.value then
Node
{ value = node.value
, left = node.left
, right = remove value node.right
}
else
case ( node.left, node.right ) of
( Empty, Empty ) ->
empty
( Empty, Node rightTree ) ->
Node rightTree
( Node leftTree, Empty ) ->
Node leftTree
( Node leftTree, Node rightTree ) ->
Node
{ value = rightTree.value
, left = Node leftTree
, right = remove rightTree.value (Node rightTree)
}
Empty ->
Empty
Tree Based, II
Strategy:
- Create types to describe the shape of a node
- Use casing to our advantage
type BinaryTree comparable
= Node comparable (BinaryTree comparable) (BinaryTree comparable)
| Empty
empty : BinaryTree comparable
empty =
Empty
new : comparable -> BinaryTree comparable
new value =
Node value empty empty
member : comparable -> BinaryTree comparable -> Bool
member value tree =
case tree of
Node nodeValue left right ->
if value < nodeValue then
member value left
else if value > nodeValue then
member value right
else
value == nodeValue
Empty ->
False
insert : comparable -> BinaryTree comparable -> BinaryTree comparable
insert value tree =
case tree of
Node nodeValue left right ->
if value < nodeValue then
Node nodeValue (insert value left) right
else if value > nodeValue then
Node nodeValue left (insert value right)
else
Node nodeValue left right
Empty ->
new value
remove : comparable -> BinaryTree comparable -> BinaryTree comparable
remove value tree =
case tree of
Node nodeValue left right ->
if value < nodeValue then
Node nodeValue (remove value left) right
else if value > nodeValue then
Node nodeValue left (remove value left)
else
case ( left, right ) of
( Empty, Empty ) ->
empty
( Empty, (Node _ _ _) as rightChild ) ->
rightChild
( (Node _ _ _) as leftChild, Empty ) ->
leftChild
( Node _ _ _, (Node rightChildValue _ _) as rightChild ) ->
Node rightChildValue left (remove value rightChild)
Empty ->
Empty
Shape of a Node
type BinaryTree comparable
= Node comparable (BinaryTree comparable) (BinaryTree comparable)
| Empty
Creating a Binary Tree
type BinaryTree comparable
= Node comparable (BinaryTree comparable) (BinaryTree comparable)
| Empty
empty : BinaryTree comparable
empty =
Empty
new : comparable -> BinaryTree comparable
new value =
Node value empty empty
Inserting Values
insert : comparable -> BinaryTree comparable -> BinaryTree comparable
insert value tree =
case tree of
Node nodeValue left right ->
if value < nodeValue then
Node nodeValue (insert value left) right
else if value > nodeValue then
Node nodeValue left (insert value right)
else
Node nodeValue left right
Empty ->
new value
Checking for Values
member : comparable -> BinaryTree comparable -> Bool
member value tree =
case tree of
Node nodeValue left right ->
if value < nodeValue then
member value left
else if value > nodeValue then
member value right
else
value == nodeValue
Empty ->
False
remove : comparable -> BinaryTree comparable -> BinaryTree comparable
remove value tree =
case tree of
Node nodeValue left right ->
if value < nodeValue then
Node nodeValue (remove value left) right
else if value > nodeValue then
Node nodeValue left (remove value left)
else
case ( left, right ) of
( Empty, Empty ) ->
empty
( Empty, (Node _ _ _) as rightChild ) ->
rightChild
( (Node _ _ _) as leftChild, Empty ) ->
leftChild
( Node _ _ _, (Node rightChildValue _ _) as rightChild ) ->
Node rightChildValue left (remove value rightChild)
Empty ->
Empty
Removing Values
Thanks!
Enjoy the rest of the conference!
@t_kelly9
type alias BinaryTree comparable =
Array (Node comparable)
type Node comparable
= Node comparable
| Empty
empty : BinaryTree comparable
empty =
Array.empty
new : comparable -> BinaryTree comparable
new value =
Array.initialize 1 (\_ -> Node value)
member : comparable -> BinaryTree comparable -> Bool
member =
memberAt 0
memberAt : Int -> comparable -> BinaryTree comparable -> Bool
memberAt index value tree =
case Array.get index tree of
Just (Node nodeValue) ->
if value < nodeValue then
memberAt (leftChild index) value tree
else if value > nodeValue then
memberAt (rightChild index) value tree
else
value == nodeValue
_ ->
False
insert : comparable -> BinaryTree comparable -> BinaryTree comparable
insert =
insertAt 0
insertAt : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
insertAt index value tree =
case Array.get index tree of
Just (Node nodeValue) ->
if value < nodeValue then
insertAt (leftChild index) value tree
else if value > nodeValue then
insertAt (rightChild index) value tree
else
tree
Just Empty ->
Array.set index (Node value) tree
Nothing ->
fillWithEmptiesUntil index value tree
fillWithEmptiesUntil : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
fillWithEmptiesUntil index value tree =
Array.repeat (index - Array.length tree) Empty
|> Array.push (Node value)
|> Array.append tree
remove : comparable -> BinaryTree comparable -> BinaryTree comparable
remove =
removeAt 0
removeAt : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
removeAt index value tree =
case Array.get index tree of
Just (Node nodeValue) ->
if value < nodeValue then
removeAt (leftChild index) value tree
else if value > nodeValue then
removeAt (rightChild index) value tree
else
case ( Array.get (leftChild index) tree, Array.get (rightChild index) tree ) of
( Just (Node leftValue), _ ) ->
Array.set index (Node leftValue) (removeAt (leftChild index) leftValue tree)
( _, Just (Node rightValue) ) ->
Array.set index (Node rightValue) (removeAt (rightChild index) rightValue tree)
( _, _ ) ->
Array.set index Empty tree
_ ->
tree
leftChild : Int -> Int
leftChild index =
2 * index + 1
rightChild : Int -> Int
rightChild index =
2 * index + 2
type alias BinaryTree comparable =
Dict Int comparable
empty : BinaryTree comparable
empty =
Dict.empty
new : comparable -> BinaryTree comparable
new value =
Dict.singleton 0 value
member : comparable -> BinaryTree comparable -> Bool
member =
memberAt 0
memberAt : Int -> comparable -> BinaryTree comparable -> Bool
memberAt index value tree =
case Dict.get index tree of
Just nodeValue ->
if value < nodeValue then
memberAt (leftChild index) value tree
else if value > nodeValue then
memberAt (rightChild index) value tree
else
value == nodeValue
_ ->
False
insert : comparable -> BinaryTree comparable -> BinaryTree comparable
insert =
insertAt 0
insertAt : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
insertAt index value tree =
case Dict.get index tree of
Just nodeValue ->
if value < nodeValue then
insertAt (leftChild index) value tree
else if value > nodeValue then
insertAt (rightChild index) value tree
else
tree
Nothing ->
Dict.insert index value tree
remove : comparable -> BinaryTree comparable -> BinaryTree comparable
remove =
removeAt 0
removeAt : Int -> comparable -> BinaryTree comparable -> BinaryTree comparable
removeAt index value tree =
case Dict.get index tree of
Just nodeValue ->
if value < nodeValue then
removeAt (leftChild index) value tree
else if value > nodeValue then
removeAt (rightChild index) value tree
else
case ( Dict.get (leftChild index) tree, Dict.get (rightChild index) tree ) of
( Just leftValue, _ ) ->
Dict.insert index leftValue (removeAt (leftChild index) leftValue tree)
( _, Just rightValue ) ->
Dict.insert index rightValue (removeAt (rightChild index) rightValue tree)
( Nothing, Nothing ) ->
Dict.remove index tree
_ ->
tree
leftChild : Int -> Int
leftChild index =
2 * index + 1
rightChild : Int -> Int
rightChild index =
2 * index + 2
type BinaryTree comparable
= Node
{ value : comparable
, left : BinaryTree comparable
, right : BinaryTree comparable
}
| Empty
empty : BinaryTree comparable
empty =
Empty
new : comparable -> BinaryTree comparable
new value =
Node
{ value = value
, left = Empty
, right = Empty
}
member : comparable -> BinaryTree comparable -> Bool
member value tree =
case tree of
Node node ->
if value < node.value then
member value node.left
else if value > node.value then
member value node.right
else
value == node.value
Empty ->
False
insert : comparable -> BinaryTree comparable -> BinaryTree comparable
insert value tree =
case tree of
Node node ->
if value < node.value then
Node
{ value = node.value
, left = insert value node.left
, right = node.right
}
else if value > node.value then
Node
{ value = node.value
, left = node.left
, right = insert value node.right
}
else
Node node
Empty ->
new value
remove : comparable -> BinaryTree comparable -> BinaryTree comparable
remove value tree =
case tree of
Node node ->
if value < node.value then
Node
{ value = node.value
, left = remove value node.left
, right = node.right
}
else if value > node.value then
Node
{ value = node.value
, left = node.left
, right = remove value node.right
}
else
case ( node.left, node.right ) of
( Empty, Empty ) ->
empty
( Empty, Node rightTree ) ->
Node rightTree
( Node leftTree, Empty ) ->
Node leftTree
( Node leftTree, Node rightTree ) ->
Node
{ value = rightTree.value
, left = Node leftTree
, right = remove rightTree.value (Node rightTree)
}
Empty ->
Empty
type BinaryTree comparable
= Node comparable (BinaryTree comparable) (BinaryTree comparable)
| Empty
empty : BinaryTree comparable
empty =
Empty
new : comparable -> BinaryTree comparable
new value =
Node value empty empty
member : comparable -> BinaryTree comparable -> Bool
member value tree =
case tree of
Node nodeValue left right ->
if value < nodeValue then
member value left
else if value > nodeValue then
member value right
else
value == nodeValue
Empty ->
False
insert : comparable -> BinaryTree comparable -> BinaryTree comparable
insert value tree =
case tree of
Node nodeValue left right ->
if value < nodeValue then
Node nodeValue (insert value left) right
else if value > nodeValue then
Node nodeValue left (insert value right)
else
Node nodeValue left right
Empty ->
new value
remove : comparable -> BinaryTree comparable -> BinaryTree comparable
remove value tree =
case tree of
Node nodeValue left right ->
if value < nodeValue then
Node nodeValue (remove value left) right
else if value > nodeValue then
Node nodeValue left (remove value left)
else
case ( left, right ) of
( Empty, Empty ) ->
empty
( Empty, (Node _ _ _) as rightChild ) ->
rightChild
( (Node _ _ _) as leftChild, Empty ) ->
leftChild
( Node _ _ _, (Node rightChildValue _ _) as rightChild ) ->
Node rightChildValue left (remove value rightChild)
Empty ->
Empty
Thanks!
Enjoy the rest of the conference!
@t_kelly9
Functional Data Structures
By Tessa K
Functional Data Structures
Elegant data structures are different across programming languages, and creating them should be approached differently. Leverage Elm’s union types to build simple and readable structures, beginning with binary trees. Writing a binary tree might be a familiar task in JavaScript or Ruby, and it might seem like a very similar problem in Elm. While it’s possible to create data structures in Elm that are very similar to ones written in languages that aren’t strictly functional and that don’t have types, there are better approaches. This talk will explore a few different implementations and recommendations, and assumes an audience without extensive academic ML-family language experience.
- 938