Una buena manera de codificar esto es para apoyarse en el recorrido proporcionada por Data.Foldable.
{-# LANGUAGE DeriveFunctor, DeriveFoldable #-}
import Data.Foldable
import Data.Monoid
Podemos obtener una instancia del mismo de forma automática utilizando una extensión, pero tenemos que cambiar el orden de los campos del nodo constructor para proporcionar un recorrido en orden.
Mientras estamos en ello, debemos eliminar las restricciones en el tipo de datos en sí. Que en realidad no proporcionan ningún beneficio, y se ha quitado de la lengua como de Haskell 2011. (Cuando se desea utilizar este tipo de limitaciones que debe ponerlos en instancias de clases, no en el tipo de datos.)
data BST a
= Void
| Node
{ left :: BST a
, val :: a
, right :: BST a
} deriving (Eq, Ord, Read, Show, Foldable)
Primero definimos lo que significa para una lista a ser estrictamente ordenadas.
sorted :: Ord a => [a] -> Bool
sorted [] = True
sorted [x] = True
sorted (x:xs) = x < head xs && sorted xs
-- head is safe because of the preceeding match.
Entonces podemos usar el toListmétodo proporcionado por Data.Foldabley el ayudante anteriormente.
isBST :: Ord a => BST a -> Bool
isBST = sorted . toList
También podemos poner en práctica esta forma más directa, como lo pediste. Puesto que hemos eliminado las restricciones espurias sobre el tipo de datos, podemos simplificar la definición de su redil.
cata :: (b -> a -> b -> b) -> b -> BST a -> b
cata _ z Void = z
cata f z (Node l x r) = f (cata f z l) x (cata f z r)
Ahora necesitamos un tipo de datos para modelar el resultado de nuestra catamorphism, que es que o bien no tienen nodos ( Z), o un rango de nodos estrictamente creciente ( T) o han fallado ( X)
data T a = Z | T a a | X deriving Eq
Y entonces podemos aplicar isBSTdirectamente
isBST' :: Ord a => BST a -> Bool
isBST' b = cata phi Z b /= X where
phi X _ _ = X
phi _ _ X = X
phi Z a Z = T a a
phi Z a (T b c) = if a < b then T a c else X
phi (T a b) c Z = if b < c then T a c else X
phi (T a b) c (T d e) = if b < c && c < d then T a e else X
Esto es un poco tedioso, por lo que quizá sería mejor para descomponer la forma en que componemos los estados intermedios un poco:
cons :: Ord a => a -> T a -> T a
cons _ X = X
cons a Z = T a a
cons a (T b c) = if a < b then T a c else X
instance Ord a => Monoid (T a) where
mempty = Z
Z `mappend` a = a
a `mappend` Z = a
X `mappend` _ = X
_ `mappend` X = X
T a b `mappend` T c d = if b < c then T a d else X
isBST'' :: Ord a => BST a -> Bool
isBST'' b = cata phi Z b /= X where
phi l a r = l `mappend` cons a r
En lo personal, yo probablemente sólo tiene que utilizar la instancia plegable.