Is there some Haskell extension that enables the creation of more complex data constructors then GADT?
Suppose I wanted to create a data structure that is an ordered list, and have a data constructor similar to 开发者_如何学编程(:)
that work with lists, with type signature:
data MyOrdList a where
(>>>) :: (Ord a) -> a -> MyOrdList a -> MyOrdList a
But I want (>>>)
to have a specific behavior, something like this:
(>>>) :: (Ord a) => a -> [a] -> [a]
x >>> [] = [x]
x >>> xs = low ++ [x] ++ high
where low = filter (<x) xs
high = filter (>x) xs
So the structure will be always an ordered structure. (I don't now if this is a good practice, I'm just offering the simplest example that occurred me of the type of behavior I want).
Of course I can use a function (>>>)
, but then I'll have no pattern matching and other benefits I'd have it >>>
was a data constructor.
Is there any way to do something like this?
You could make MyOrdList
an abstract type and (>>>)
a function and use view patterns. For simplicity, I use a standard list as a "backend" here.
module MyOrdList
(MyOrdList,
MyOrdListView (OrdNil, OrdCons),
(>>>),
emptyOrdList,
ordview
) where
import Data.List (sort)
newtype MyOrdList a = List [a]
deriving Show
data MyOrdListView a = OrdNil | OrdCons a (MyOrdList a)
infixr 5 >>>
(>>>) :: (Ord a) => a -> MyOrdList a -> MyOrdList a
x >>> (List xs) = List (sort $ x:xs)
emptyOrdList = List []
ordview :: MyOrdList a -> MyOrdListView a
ordview (List []) = OrdNil
ordview (List (x:xs)) = OrdCons x (List xs)
You can use it like that:
{-# LANGUAGE ViewPatterns #-}
import MyOrdList
ordlength :: MyOrdList a -> Int
ordlength (ordview -> OrdNil) = 0
ordlength (ordview -> OrdCons x xs) = 1 + ordlength xs
Works:
*Main> ordlength $ 2 >>> 3 >>> 1 >>> emptyOrdList
3
*Main> 2 >>> 3 >>> 1 >>> emptyOrdList
List [1,2,3]
So your type is abstract, lists can only be constructed by emptyOrdList
and (>>>)
but you still have some pattern matching convenience.
You could make :>>>
a data constructor, but you'd have to hide it to preserve your invariant. Notice that you can pattern-match against it as in render
:
module MyOrdList (mkMyOrdList,MyOrdList,render) where
import Data.List
import qualified Data.ByteString as BS
data MyOrdList a
= EmptyDataList
| (:>>>) a (MyOrdList a)
deriving (Show)
mkMyOrdList [] = EmptyDataList
mkMyOrdList xs = y :>>> mkMyOrdList ys
where y = minimum xs
ys = delete y xs
render :: (Show a) => MyOrdList a -> String
render EmptyDataList = "<empty>"
render (x :>>> xs) = (show x) ++ " -> " ++ render xs
Then you might use the MyOrdList
module as in
module Main where
import Control.Applicative
import System.IO
import qualified Data.ByteString as BS
import MyOrdList
main = do
h <- openBinaryFile "/dev/urandom" ReadMode
cs <- readBytes 10 h
-- but you cannot write...
-- let bad = 3 :>>> 2 :>>> 1 :>>> EmptyOrdList
putStrLn (render $ mkMyOrdList cs)
where
readBytes 0 _ = return []
readBytes n h = do c <- BS.head <$> BS.hGet h 1
cs <- readBytes (n-1) h
return (c:cs)
Sample output:
54 -> 57 -> 64 -> 98 -> 131 -> 146 -> 147 -> 148 -> 190 -> 250 -> <empty>
精彩评论