Find ancestors of node in BST in Standard ML - binary-search-tree

I'm trying to find the parent and grandparent of a node in a BST in SML.
I have tried to modify this function:
fun search(tree : bst, compare, (data) =
let fun s(Leaf) = NONE
| s(Node{data=nodedata,left=left,right=right}) =
(case compare(data,nodedata) of
LESS => s(left)
| GREATER => s(right)
| EQUAL => SOME (Node{data=nodedata,left=left,right=right}))
in
s(tree)
end
by changing the format of the input, the comparison and the output:
fun search(tree : bst, compare, (data) =
let fun s(Leaf) = NONE
|s(Node{data=nodedata,left=Node{data=nodedata1,left=left1,right=right1},right=right}) =
(case compare(data,nodedata) of
LESS => s(left)
| GREATER => s(right)
| EQUAL => SOME nodedata)
Obviously it's not working and I suppose there is a simpler way to do this.
I added a new variable in order to save the current father of each node I'm checking and it works, however it's difficult (for me) to find a way to also keep the grandfather of the node I check.
Any ideas? Thanks in advance.

First of all, your search function was not working properly, so I cleaned it up a little bit. In order to avoid unbound type variable in type declaration error, you have to define your bst type as 'a bst. I also fixed a paranthesis error. Here is my version of your search function:
datatype 'a bst = Leaf | Node of {data: 'a, left: 'a bst, right: 'a bst}
(*search(tree, compare, key) = t
where t = the tree which has `key` as its root
*)
fun search(tree : 'a bst, compare : ('a * 'a -> order), key : 'a)
: 'a bst option =
let
fun s(tree: 'a bst) : 'a bst option =
case tree of
Leaf => NONE
| Node({data=x, left=l, right=r}) =>
case compare(key,x) of
LESS => s(l)
| GREATER => s(r)
| EQUAL => SOME tree
in
s(tree)
end
(*Example:
search(Node({data=5,
left=Node({data=3,
left=Node({data=2,left=Leaf,right=Leaf}),
right=Node({data=4,left=Leaf,right=Leaf})}),
right=Leaf}),
Int.compare,
3);
*)
Since you want a function that returns the parent and grandparent nodes of your search, you need a function that returns a tuple of these: (search node, parent of the search node, grandparent of the search node). However, you will not always have a parent or grandparent. For example, if the key you are looking for is already at the root of your main tree, you will not have a parent or grandparent. Because of that, you need to use the option type. This will help you return NONE as the parent node if there is no parent node for that search. Now, all you need to do is to start the search with (your main tree, NONE, NONE) and then as you move along the tree, shift the parent nodes to the right. If you don't find the key, just return a triplet of NONE. Here is the code for that:
(*searchParent(tree, compare, key) =
(t, the parent of t, the grandparent of t)
where t = the tree which has `key` as its root
*)
fun searchParent(tree : 'a bst, compare : ('a * 'a -> order), key : 'a)
: ('a bst option * 'a bst option * 'a bst option) =
let
fun s(tree: 'a bst, par: 'a bst option, gpar: 'a bst option)
: ('a bst option * 'a bst option * 'a bst option) =
case tree of
Leaf => (NONE, NONE, NONE)
| Node({data=x, left=l, right=r}) =>
case compare(key,x) of
LESS => s(l, SOME tree, par)
| GREATER => s(r, SOME tree, par)
| EQUAL => (SOME tree, par, gpar)
in
s(tree, NONE, NONE)
end
(*Example:
searchParent(Node({data=5,
left=Node({data=3,
left=Node({data=2,left=Leaf,right=Leaf}),
right=Node({data=4,left=Leaf,right=Leaf})}),
right=Leaf}),
Int.compare,
4);
*)
I hope I could help. Please ask if you have any other questions on this issue or code.

While joom's answer is probably sufficient, I find it a little verbose. Here is an attempt to write a shorter interpretation of and answer to the problem: Having a BST and an element x, find the two elements (SOME parent, SOME grandparent) if they exist.
The strategy I will use is this: Whenever a recursive call is made, record parent as the element that was just visited and grandparent as the previous parent. If/when an element is found that is EQUAL to x, return the currently available (parent, grandparent).
Because these two additional variables are not part of the original search function's signature, and because I don't want them to be, the local function helper is made.
datatype 'a bst = Node of 'a * 'a bst * 'a bst
| Leaf
fun search (initTree, compare, x) =
let fun helper (tree, parent, grandparent) =
case tree of
Leaf => (NONE, NONE)
| Node (y, L, R) => case compare (x, y) of
LESS => helper (L, SOME y, parent)
| GREATER => helper (R, SOME y, parent)
| EQUAL => (parent, grandparent)
in helper (initTree, NONE, NONE) end
Testing this function using the following BST:
val t = Node (5, Node (3, Node (2, Leaf, Leaf),
Node (4, Leaf, Leaf)),
Node (7, Node (6, Leaf, Leaf),
Node (8, Leaf, Leaf)))
We see the expected results:
- search (t, Int.compare, 2);
> val it = (SOME 3, SOME 5) : int option * int option
- search (t, Int.compare, 3);
> val it = (SOME 5, NONE) : int option * int option
Unfortunately, this function will not tell whether an element was found in the root of the BST or did not exist. That is because (NONE, NONE) is returned in both cases.

Related

In Idris, how can I enforce ordering in a data type for a binary search tree

I'm having problems to enforce the ordering in a binary search tree in Idris, and I'm not even sure I'm using the right approach.
Coming from Liquid Haskell, the idea behind the implementation is creating a simple binary tree and then refining the types in the initial structure (as it can be seen here https://ucsd-progsys.github.io/liquidhaskell-tutorial/Tutorial_12_Case_Study_AVL.html).
When trying to translate the same ordering restriction to Idris, things get a bit thorny.
Here's what I'm doing, first, I start with a simple definition of a binary tree, same as Haskell.
data Tree : (a: Type) -> Type where
Leaf : Tree a
Node : (key: a) -> (left: Tree a) -> (right: Tree a) -> Tree a
Having this in hand (and following up this question: Encoding a binary search tree in data type), I constructed another data type that should enforce the "search" part of the binary search tree, as follows:
minVal : Ord a => Tree a -> Maybe a
minVal Leaf = Nothing
minVal (Node key Leaf _) = Just key
minVal (Node key left right) = minVal left
maxVal : Ord a => Tree a -> Maybe a
maxVal Leaf = Nothing
maxVal (Node key _ Leaf) = Just key
maxVal (Node key left right) = maxVal right
lessTree : Ord a => a -> Tree a -> Bool
lessTree x node = case minVal node of
Nothing => True
Just key => x < key
moreTree : Ord a => a -> Tree a -> Bool
moreTree x node = case maxVal node of
Nothing => True
Just key => x >= key
IsLft : Ord a => (x: a) -> (left: Tree a) -> Type
IsLft x left = So (moreTree x left)
IsRgt : Ord a => (x: a) -> (right: Tree a) -> Type
IsRgt x right = So (lessTree x right)
data IsBST : (t : Tree a) -> Type where
IsBSTLeaf : IsBST Leaf
IsBSTNode : Ord a => (x: a) -> (IsBST left) -> (IsLft x left) -> (IsBST right) -> (IsRgt x right) -> (IsBST (Node x left right))
BSTree : Type -> Type
BSTree a = (t : (Tree a) ** (IsBST t))
Basically, the "proof" that a BST is a BST would be that the left tree is also a BST and should be in the left side (as in its root value is smaller than its parent), and the same idea for the right side.
This code compiles, but it doesn't work as expected, it can be seen in the implementation of an insert function, as below:
data Comp = LT | EQ | GT
comp : Ord a => a -> a -> Comp
comp x y = if x > y then GT else if x < y then LT else EQ
wrongInsert : Ord a => (x : a) -> BSTree a -> BSTree a
wrongInsert x (Leaf ** IsBSTLeaf) =
let isLftPrf = mkIsLft x Leaf
isRgtPrf = mkIsRgt x Leaf
in ((Node x Leaf Leaf) ** (IsBSTNode x IsBSTLeaf isLftPrf IsBSTLeaf isRgtPrf))
wrongInsert x ((Node y left right) ** (IsBSTNode y lPrf isLftPrf rPrf isRgtPrf)) =
case comp y x of
LT =>
let (lTree ** pl) = wrongInsert x (left ** lPrf)
isLft = mkIsLft y lTree
in ((Node y lTree right) ** (IsBSTNode y pl isLft rPrf isRgtPrf))
GT =>
let (rTree ** pr) = wrongInsert x (right ** rPrf)
isRgt = mkIsRgt y rTree
in ((Node y left rTree) ** (IsBSTNode y lPrf isLftPrf pr isRgt))
EQ => ((Node y left right) ** (IsBSTNode y lPrf isLftPrf rPrf isRgtPrf))
In that case, the "LT" and "GT" parts are inverted, as in something greater than the root is being inserted in the left side, and vice-versa, so it shouldn't be possible to create such a tree, yet it works, which implies that Idris is accepting the creation of a "So False" (which I thought shouldn't be impossible, since there's only a constructor for True from what I've looked at).
From what I read on other sources, maybe using "So" is not the better idea here, but I fail to see other way to create a restriction in Idris as I have in Liquid Haskell, so that the data type itself prevents me from implementing a wrong insert function, as in the example above. Is that even possible, or if this approach is completely wrong, what would be the right way of implementing such a restriction?

How should I model a type-safe index in Purescript?

In my application, I'd like to index sets of objects in a type-safe way using a structure similar to a relational database index. For example, I might want to index a set of User objects based on age and name:
import Data.Map as M
import Data.Set as S
type AgeNameIndex = M.Map Int (M.Map String (S.Set User))
Furthermore, I'd like to do operations like union and difference on indexes efficiently, e.g.:
let a = M.singleton 42 $ M.singleton "Bob" $ S.singleton $ User { ... }
b = M.singleton 42 $ M.singleton "Tim" $ S.singleton $ User { ... }
c = union a b -- contains both Bob and Tim
I've tried to model this as follows:
module Concelo.Index
( index
, union
, subtract
, lastValue
, subIndex ) where
import Prelude (($), (>>>), flip, Unit, unit, class Ord)
import Control.Monad ((>>=))
import Data.Map as M
import Data.Set as S
import Data.Maybe (Maybe(Nothing, Just), fromMaybe)
import Data.Tuple (Tuple(Tuple))
import Data.Foldable (foldl)
import Data.Monoid (mempty)
class Index index key value subindex where
isEmpty :: index -> Boolean
union :: index -> index -> index
subtract :: index -> index -> index
lastValue :: index -> Maybe value
subIndex :: key -> index -> subindex
instance mapIndex :: (Index subindex subkey value subsubindex) =>
Index (M.Map key subindex) key value subindex where
isEmpty = M.isEmpty
union small large =
foldl (m (Tuple k v) -> M.alter (combine v) k m) large (M.toList small)
where
combine v = case _ of
Just v' -> Just $ union v v'
Nothing -> Just v
subtract small large =
foldl (m (Tuple k v) -> M.alter (minus v) k m) large (M.toList small)
where
minus v = (_ >>= v' ->
let subindex = subtract v v' in
if isEmpty subindex then Nothing else Just subindex)
lastValue m = M.findMax m >>= (_.value >>> lastValue)
subIndex k m = fromMaybe mempty $ M.lookup k m
instance setIndex :: (Ord value) => Index (S.Set value) Unit value Unit where
isEmpty = S.isEmpty
union = S.union
subtract = flip S.difference
lastValue s = Nothing -- todo: S.findMax
subIndex _ _ = unit
index f = foldl (acc v -> union (f v) acc) mempty
However, the Purescript compiler doesn't like that:
Compiling Concelo.Index
Error found:
in module Concelo.Index
at /home/dicej/p/pssync/src/Concelo/Index.purs line 24, column 1 - line 44, column 49
No type class instance was found for
Concelo.Index.Index subindex0
t1
t2
t3
The instance head contains unknown type variables. Consider adding a type annotation.
in value declaration mapIndex
where subindex0 is a rigid type variable
t1 is an unknown type
t2 is an unknown type
t3 is an unknown type
See https://github.com/purescript/purescript/wiki/Error-Code-NoInstanceFound for more information,
or to contribute content related to this error.
My understanding of this message is that I haven't properly stated that map values in the mapIndex instance are themselves Index instances, but I don't know how to fix that. Where might I add a type annotation to make this compile? Or am I even on the right track given what I'm trying to do?
This is almost certainly because PureScript currently lacks functional dependencies (or type families) which makes this kind of information un-inferrable. There's a writeup of the issue here: https://github.com/purescript/purescript/issues/1580 - it is something we want to support.
There was a discussion about a case very similar to this today as it happens: https://github.com/purescript/purescript/issues/2235
Essentially, the problem here is that the functions of the class do not use all of the type variables, which means there's no way to propagate the information to the constraint for looking up a suitable instance.
I don't really have a suggestion for how to do what you're after here with things as they are, aside from avoiding the class and implementing it with specific types in mind.

What's the difference between abstraction and generalization?

I understand that abstraction is about taking something more concrete and making it more abstract. That something may be either a data structure or a procedure. For example:
Data abstraction: A rectangle is an abstraction of a square. It concentrates on the fact a square has two pairs of opposite sides and it ignores the fact that adjacent sides of a square are equal.
Procedural abstraction: The higher order function map is an abstraction of a procedure which performs some set of operations on a list of values to produce an entirely new list of values. It concentrates on the fact that the procedure loops through every item of the list in order to produce a new list and ignores the actual operations performed on every item of the list.
So my question is this: how is abstraction any different from generalization? I'm looking for answers primarily related to functional programming. However if there are parallels in object-oriented programming then I would like to learn about those as well.
A very interesting question indeed. I found this article on the topic, which concisely states that:
While abstraction reduces complexity by hiding irrelevant detail, generalization reduces complexity by replacing multiple entities which perform similar functions with a single construct.
Lets take the old example of a system that manages books for a library. A book has tons of properties (number of pages, weight, font size(s), cover,...) but for the purpose of our library we may only need
Book(title, ISBN, borrowed)
We just abstracted from the real books in our library, and only took the properties that interested us in the context of our application.
Generalization on the other hand does not try to remove detail but to make functionality applicable to a wider (more generic) range of items. Generic containers are a very good example for that mindset: You wouldn't want to write an implementation of StringList, IntList, and so on, which is why you'd rather write a generic List which applies to all types (like List[T] in Scala). Note that you haven't abstracted the list, because you didn't remove any details or operations, you just made them generically applicable to all your types.
Round 2
#dtldarek's answer is really a very good illustration! Based on it, here's some code that might provide further clarification.
Remeber the Book I mentioned? Of course there are other things in a library that one can borrow (I'll call the set of all those objects Borrowable even though that probably isn't even a word :D):
All of these items will have an abstract representation in our database and business logic, probably similar to that of our Book. Additionally, we might define a trait that is common to all Borrowables:
trait Borrowable {
def itemId:Long
}
We could then write generalized logic that applies to all Borrowables (at that point we don't care if its a book or a magazine):
object Library {
def lend(b:Borrowable, c:Customer):Receipt = ...
[...]
}
To summarize: We stored an abstract representation of all the books, magazines and DVDs in our database, because an exact representation is neither feasible nor necessary. We then went ahead and said
It doesn't matter whether a book, a magazine or a DVD is borrowed by a customer. It's always the same process.
Thus we generalized the operation of borrowing an item, by defining all things that one can borrow as Borrowables.
Object:
Abstraction:
Generalization:
Example in Haskell:
The implementation of the selection sort by using priority queue with three different interfaces:
an open interface with the queue being implemented as a sorted list,
an abstracted interface (so the details are hidden behind the layer of abstraction),
a generalized interface (the details are still visible, but the implementation is more flexible).
{-# LANGUAGE RankNTypes #-}
module Main where
import qualified Data.List as List
import qualified Data.Set as Set
{- TYPES: -}
-- PQ new push pop
-- by intention there is no build-in way to tell if the queue is empty
data PriorityQueue q t = PQ (q t) (t -> q t -> q t) (q t -> (t, q t))
-- there is a concrete way for a particular queue, e.g. List.null
type ListPriorityQueue t = PriorityQueue [] t
-- but there is no method in the abstract setting
newtype AbstractPriorityQueue q = APQ (forall t. Ord t => PriorityQueue q t)
{- SOLUTIONS: -}
-- the basic version
list_selection_sort :: ListPriorityQueue t -> [t] -> [t]
list_selection_sort (PQ new push pop) list = List.unfoldr mypop (List.foldr push new list)
where
mypop [] = Nothing -- this is possible because we know that the queue is represented by a list
mypop ls = Just (pop ls)
-- here we abstract the queue, so we need to keep the queue size ourselves
abstract_selection_sort :: Ord t => AbstractPriorityQueue q -> [t] -> [t]
abstract_selection_sort (APQ (PQ new push pop)) list = List.unfoldr mypop (List.foldr mypush (0,new) list)
where
mypush t (n, q) = (n+1, push t q)
mypop (0, q) = Nothing
mypop (n, q) = let (t, q') = pop q in Just (t, (n-1, q'))
-- here we generalize the first solution to all the queues that allow checking if the queue is empty
class EmptyCheckable q where
is_empty :: q -> Bool
generalized_selection_sort :: EmptyCheckable (q t) => PriorityQueue q t -> [t] -> [t]
generalized_selection_sort (PQ new push pop) list = List.unfoldr mypop (List.foldr push new list)
where
mypop q | is_empty q = Nothing
mypop q | otherwise = Just (pop q)
{- EXAMPLES: -}
-- priority queue based on lists
priority_queue_1 :: Ord t => ListPriorityQueue t
priority_queue_1 = PQ [] List.insert (\ls -> (head ls, tail ls))
instance EmptyCheckable [t] where
is_empty = List.null
-- priority queue based on sets
priority_queue_2 :: Ord t => PriorityQueue Set.Set t
priority_queue_2 = PQ Set.empty Set.insert Set.deleteFindMin
instance EmptyCheckable (Set.Set t) where
is_empty = Set.null
-- an arbitrary type and a queue specially designed for it
data ABC = A | B | C deriving (Eq, Ord, Show)
-- priority queue based on counting
data PQ3 t = PQ3 Integer Integer Integer
priority_queue_3 :: PriorityQueue PQ3 ABC
priority_queue_3 = PQ new push pop
where
new = (PQ3 0 0 0)
push A (PQ3 a b c) = (PQ3 (a+1) b c)
push B (PQ3 a b c) = (PQ3 a (b+1) c)
push C (PQ3 a b c) = (PQ3 a b (c+1))
pop (PQ3 0 0 0) = undefined
pop (PQ3 0 0 c) = (C, (PQ3 0 0 (c-1)))
pop (PQ3 0 b c) = (B, (PQ3 0 (b-1) c))
pop (PQ3 a b c) = (A, (PQ3 (a-1) b c))
instance EmptyCheckable (PQ3 t) where
is_empty (PQ3 0 0 0) = True
is_empty _ = False
{- MAIN: -}
main :: IO ()
main = do
print $ list_selection_sort priority_queue_1 [2, 3, 1]
-- print $ list_selection_sort priority_queue_2 [2, 3, 1] -- fail
-- print $ list_selection_sort priority_queue_3 [B, C, A] -- fail
print $ abstract_selection_sort (APQ priority_queue_1) [B, C, A] -- APQ hides the queue
print $ abstract_selection_sort (APQ priority_queue_2) [B, C, A] -- behind the layer of abstraction
-- print $ abstract_selection_sort (APQ priority_queue_3) [B, C, A] -- fail
print $ generalized_selection_sort priority_queue_1 [2, 3, 1]
print $ generalized_selection_sort priority_queue_2 [B, C, A]
print $ generalized_selection_sort priority_queue_3 [B, C, A]-- power of generalization
-- fail
-- print $ let f q = (list_selection_sort q [2,3,1], list_selection_sort q [B,C,A])
-- in f priority_queue_1
-- power of abstraction (rank-n-types actually, but never mind)
print $ let f q = (abstract_selection_sort q [2,3,1], abstract_selection_sort q [B,C,A])
in f (APQ priority_queue_1)
-- fail
-- print $ let f q = (generalized_selection_sort q [2,3,1], generalized_selection_sort q [B,C,A])
-- in f priority_queue_1
The code is also available via pastebin.
Worth noticing are the existential types. As #lukstafi already pointed out, abstraction is similar to existential quantifier and generalization is similar to universal quantifier.
Observe that there is a non-trivial connection between the fact that ∀x.P(x) implies ∃x.P(x) (in a non-empty universe), and that there rarely is a generalization without abstraction (even c++-like overloaded functions form a kind of abstraction in some sense).
Credits:
Portal cake by Solo.
Dessert table by djttwo.
The symbol is the cake icon from material.io.
I'm going to use some examples to describe generalisation and abstraction, and I'm going to refer to this article.
To my knowledge, there is no official source for the definition of abstraction and generalisation in the programming domain (Wikipedia is probably the closest you'll get to an official definition in my opinion), so I've instead used an article which I deem credible.
Generalization
The article states that:
"The concept of generalization in OOP means that an object encapsulates
common state and behavior for a category of objects."
So for example, if you apply generalisation to shapes, then the common properties for all types of shape are area and perimeter.
Hence a generalised shape (e.g. Shape) and specialisations of it (e.g. a Circle), can be represented in classes as follows (note that this image has been taken from the aforementioned article)
Similarly, if you were working in the domain of jet aircraft, you could have a Jet as a generalisation, which would have a wingspan property. A specialisation of a Jet could be a FighterJet, which would inherit the wingspan property and would have its own property unique to fighter jets e.g. NumberOfMissiles.
Abstraction
The article defines abstraction as:
"the process of identifying common patterns that have systematic
variations; an abstraction represents the common pattern and provides
a means for specifying which variation to use" (Richard Gabriel)"
In the domain of programming:
An abstract class is a parent class that allows inheritance but can
never be instantiated.
Hence in the example given in the Generalization section above, a Shape is abstract as:
In the real world, you never calculate the area or perimeter of a
generic shape, you must know what kind of geometric shape you have
because each shape (eg. square, circle, rectangle, etc.) has its own
area and perimeter formulas.
However, as well as being abstract a shape is also a generalisation (because it "encapsulates common state and behavior for a category of objects" where in this case the objects are shapes).
Going back to the example I gave about Jets and FighterJets, a Jet is not abstract as a concrete instance of a Jet is feasible, as one can exist in the real world, unlike a shape i.e. in the real world you cant hold a shape you hold an instance of a shape e.g. a cube. So in the aircraft example, a Jet is not abstract, it is a generalisation as it is possible to have a "concrete" instance of a jet.
Not addressing credible / official source: an example in Scala
Having "Abstraction"
trait AbstractContainer[E] { val value: E }
object StringContainer extends AbstractContainer[String] {
val value: String = "Unflexible"
}
class IntContainer(val value: Int = 6) extends AbstractContainer[Int]
val stringContainer = new AbstractContainer[String] {
val value = "Any string"
}
and "Generalization"
def specialized(c: StringContainer.type) =
println("It's a StringContainer: " + c.value)
def slightlyGeneralized(s: AbstractContainer[String]) =
println("It's a String container: " + s.value)
import scala.reflect.{ classTag, ClassTag }
def generalized[E: ClassTag](a: AbstractContainer[E]) =
println(s"It's a ${classTag[E].toString()} container: ${a.value}")
import scala.language.reflectiveCalls
def evenMoreGeneral(d: { def detail: Any }) =
println("It's something detailed: " + d.detail)
executing
specialized(StringContainer)
slightlyGeneralized(stringContainer)
generalized(new IntContainer(12))
evenMoreGeneral(new { val detail = 3.141 })
leads to
It's a StringContainer: Unflexible
It's a String container: Any string
It's a Int container: 12
It's something detailed: 3.141
Abstraction
Abstraction is specifying the framework and hiding the implementation level information. Concreteness will be built on top of the abstraction. It gives you a blueprint to follow to while implementing the details. Abstraction reduces the complexity by hiding low level details.
Example: A wire frame model of a car.
Generalization
Generalization uses a “is-a” relationship from a specialization to the generalization class. Common structure and behaviour are used from the specializtion to the generalized class. At a very broader level you can understand this as inheritance. Why I take the term inheritance is, you can relate this term very well. Generalization is also called a “Is-a” relationship.
Example: Consider there exists a class named Person. A student is a person. A faculty is a person. Therefore here the relationship between student and person, similarly faculty and person is generalization.
I'd like to offer an answer for the greatest possible audience, hence I use the Lingua Franca of the web, Javascript.
Let's start with an ordinary piece of imperative code:
// some data
const xs = [1,2,3];
// ugly global state
const acc = [];
// apply the algorithm to the data
for (let i = 0; i < xs.length; i++) {
acc[i] = xs[i] * xs[i];
}
console.log(acc); // yields [1, 4, 9]
In the next step I introduce the most important abstraction in programming - functions. Functions abstract over expressions:
// API
const foldr = f => acc => xs => xs.reduceRight((acc, x) => f(x) (acc), acc);
const concat = xs => ys => xs.concat(ys);
const sqr_ = x => [x * x]; // weird square function to keep the example simple
// some data
const xs = [1,2,3];
// applying
console.log(
foldr(x => acc => concat(sqr_(x)) (acc)) ([]) (xs) // [1, 4, 9]
)
As you can see a lot of implementation details are abstracted away. Abstraction means the suppression of details.
Another abstraction step...
// API
const comp = (f, g) => x => f(g(x));
const foldr = f => acc => xs => xs.reduceRight((acc, x) => f(x) (acc), acc);
const concat = xs => ys => xs.concat(ys);
const sqr_ = x => [x * x];
// some data
const xs = [1,2,3];
// applying
console.log(
foldr(comp(concat, sqr_)) ([]) (xs) // [1, 4, 9]
);
And another one:
// API
const concatMap = f => foldr(comp(concat, f)) ([]);
const comp = (f, g) => x => f(g(x));
const foldr = f => acc => xs => xs.reduceRight((acc, x) => f(x) (acc), acc);
const concat = xs => ys => xs.concat(ys);
const sqr_ = x => [x * x];
// some data
const xs = [1,2,3];
// applying
console.log(
concatMap(sqr_) (xs) // [1, 4, 9]
);
The underlying principle should now be clear. I'm still dissatisfied with concatMap though, because it only works with Arrays. I want it to work with every data type that is foldable:
// API
const concatMap = foldr => f => foldr(comp(concat, f)) ([]);
const concat = xs => ys => xs.concat(ys);
const sqr_ = x => [x * x];
const comp = (f, g) => x => f(g(x));
// Array
const xs = [1, 2, 3];
const foldr = f => acc => xs => xs.reduceRight((acc, x) => f(x) (acc), acc);
// Option (another foldable data type)
const None = r => f => r;
const Some = x => r => f => f(x);
const foldOption = f => acc => tx => tx(acc) (x => f(x) (acc));
// applying
console.log(
concatMap(foldr) (sqr_) (xs), // [1, 4, 9]
concatMap(foldOption) (sqr_) (Some(3)), // [9]
concatMap(foldOption) (sqr_) (None) // []
);
I broadened the application of concatMap to encompass a larger domain of data types, nameley all foldable datatypes. Generalization emphasizes the commonalities between different types, (or rather objects, entities).
I achieved this by means of dictionary passing (concatMap's additional argument in my example). Now it is somewhat annoying to pass these type dicts around throughout your code. Hence the Haskell folks introduced type classes to, ...um, abstract over type dicts:
concatMap :: Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\x -> [x * x]) ([1,2,3]) -- yields [1, 4, 9]
concatMap (\x -> [x * x]) (Just 3) -- yields [9]
concatMap (\x -> [x * x]) (Nothing) -- yields []
So Haskell's generic concatMap benefits from both, abstraction and generalization.
Let me explain in the simplest manner possible.
"All pretty girls are female." is an abstraction.
"All pretty girls put on make-up." is a generalization.
Abstraction is usually about reducing complexity by eliminating unnecessary details. For example, an abstract class in OOP is a parent class that contains common features of its children but does not specify the exact functionality.
Generalization does not necessarily require to avoid details but rather to have some mechanism to allow for applying the same function to different argument. For instance, polymorphic types in functional programming languages allow you not to bother about the arguments, rather focus on the operation of the function. Similarly, in java you can have generic type which is an "umbrella" to all types while the function is the same.
Here is a more general (pun intended) description.
abstraction operation
changes the representation of an entity by hiding/reducing its properties that are not necessary for the desired conceptualization
has an inherent information loss, which makes it less flexible but more conclusive
generalization operation
doesn't change the representation of an entity, but defines similarities between entities of different kind
applies knowledge previously acquired to unseen circumstances or extends that knowledge beyond the scope of the original problem (knowledge transfer)
can be seen as a hypothesis that a set of entities of different kind have similar properties and will behave consistently when applied in a certain way
has no inherent information loss, which makes it more flexible but less conclusive (more error-prone)
Both operations reduce complexity either by hiding details or by reducing entities that perform similar functions to a single construct.

What does 'int ?. tree' mean in an SML error message?

I have the following SML code that I wrote for a class:
fun lookup (cmp: 'a * 'a -> order) (x: 'a, t: 'a tree) : 'a option =
case t of
Empty => NONE
| Node(l,y,r) =>
case cmp(x,y) of
EQUAL => SOME y
| LESS => lookup (cmp) (x,r)
| GREATER => lookup (cmp) (x,l)
In testing this with:
val SOME 3 = lookup Int.compare (3, Node(Empty,3,Empty));
And getting the following error back:
stdIn:153.1-166.12 Error: operator and operand don't agree [tycon mismatch]
operator domain: int * int ?.tree
operand: int * int tree
in expression:
(lookup Int.compare) (3,Node (Empty,3,Empty))
What does the ?. mean?
This is usually to do with restricted visibility across modules. What does your 'tree' definition look like? You might need to tell the compiler that your 'tree' type in one module is the same as the one in another module.

Can I avoid committing to particular types in a module type and still get pattern matching?

I have two module types:
module type ORDERED =
sig
type t
val eq : t * t -> bool
val lt : t * t -> bool
val leq : t * t -> bool
end
module type STACK =
sig
exception Empty
type 'a t
val empty : 'a t
val isEmpty : 'a t -> bool
val cons : 'a * 'a t -> 'a t
val head : 'a t -> 'a
val tail : 'a t -> 'a t
val length : 'a t -> int
end
I want to write a functor which "lifts" the order relation from the basic ORDERED type to STACKs of that type. That can be done by saying that, for example, two stacks of elements will be equal if all its individual elements are equal. And that stacks s1 and s2 are s.t. s1 < s2 if the first of each of their elements, e1 and e2, are also s.t. e1 < e2, etc.
Now, if don't commit to explicitly defining the type in the module type, I will have to write something like this (or won't I?):
module StackLift (O : ORDERED) (S : STACK) : ORDERED =
struct
type t = O.t S.t
let rec eq (x,y) =
if S.isEmpty x
then if S.isEmpty y
then true
else false
else if S.isEmpty y
then false
else if O.eq (S.head x,S.head y)
then eq (S.tail x, S.tail y)
else false
(* etc for lt and leq *)
end
which is a very clumsy way of doing what pattern matching serves so well. An alternative would be to impose the definition of type STACK.t using explicit constructors, but that would tie my general module somewhat to a particular implementation, which I don't want to do.
Question: can I define something different above so that I can still use pattern matching while at the same time keeping the generality of the module types?
As an alternative or supplement to the other access functions, the module can provide a view function that returns a variant type to use in pattern matching.
type ('a, 's) stack_view = Nil | Cons of 'a * 's
module type STACK =
sig
val view : 'a t -> ('a , 'a t) stack_view
...
end
module StackLift (O : ORDERED) (S : STACK) : ORDERED =
struct
let rec eq (x, y) =
match S.view x, S.view y with
Cons (x, xs), Cons (y, ys) -> O.eq (x, y) && eq (xs, ys)
| Nil, Nil -> true
| _ -> false
...
end
Any stack with a head and tail function can have a view function too, regardless of the underlying data structure.
I believe you've answered your own question. A module type in ocaml is an interface which you cannot look behind. Otherwise, there's no point. You cannot keep the generality of the interface while exposing details of the implementation. The only thing you can use is what's been exposed through the interface.
My answer to your question is yes, there might be something you can do to your definition of stack, that would make the type of a stack a little more complex, thereby making it match a different pattern than just a single value, like (val,val) for instance. However, you've got a fine definition of a stack to work with, and adding more type-fluff is probably a bad idea.
Some suggestions with regards to your definitions:
Rename the following functions: cons => push, head => peek, tail => pop_. I would also add a function val pop : 'a t -> 'a * 'a t, in order to combine head and tail into one function, as well as to mirror cons. Your current naming scheme seems to imply that a list is backing your stack, which is a mental leak of the implementation :D.
Why do eq, lt, and leq take a pair as the first parameter? In constraining the type of eq to be val eq : 'a t * 'a t -> 'a t, you're forcing the programmer that uses your interface to keep around one side of the equality predicate until they've got the other side, before finally applying the function. Unless you have a very good reason, I would use the default curried form of the function, since it provides a little more freedom to the user (val eq : 'a t -> 'a t -> 'a t). The freedom comes in that they can partially apply eq and pass the function around instead of the value and function together.