Haskell data, custom string values - haskell

I am writing a Haskell SDK, I have everything working however I'm wanting to introduce stronger types to my search filters (url parameters).
A sample call looks like:
-- list first 3 positive comments mentioned by females
comments "tide-pods" [("limit", "3"),("sentiment", "positive"),("gender", "female")] config
While this isn't too horrible for me, I would really like to be able to pass in something like:
comments "tide-pods" [("limit", "3"),(Sentiment, Positive),(Gender, Male)] config
Or something similar.
In DataRank.hs you can see my url parameter type type QueryParameter = (String, String), as well as the code to convert the arguments for http-conduit convertParameters :: [QueryParameter] -> [(ByteString, Maybe ByteString)]
I have been experimenting with data/types, for example:
data Gender = Male | Female | Any
-- desired values of above data types
-- Male = "male"
-- Female = "female"
-- Any = "male,female"
The api also needs to remain flexible enough for any arbitrary String key, String values because I would like the SDK to keep the ability to supply new filters without depending on a SDK update. For the curious, A list of the search filters to-date are in a recently built Java SDK
I was having problems finding a good way to provide the search interface in Haskell. Thanks in advance!

The simplest way to keep it simple but unsafe is to just use a basic ADT with an Arbitrary field that takes a String key and value:
data FilterKey
= Arbitrary String String
| Sentiment Sentiment
| Gender Gender
deriving (Eq, Show)
data Sentiment
= Positive
| Negative
| Neutral
deriving (Eq, Show, Bounded, Enum)
data Gender
= Male
| Female
| Any
deriving (Eq, Show, Bounded, Enum)
Then you need a function to convert a FilterKey to your API's base (String, String) filter type
filterKeyToPair :: FilterKey -> (String, String)
filterKeyToPair (Arbitrary key val) = (key, val)
filterKeyToPair (Sentiment sentiment) = ("sentiment", showSentiment sentiment)
filterKeyToPair (Gender gender) = ("gender", showGender gender)
showSentiment :: Sentiment -> String
showSentiment s = case s of
Positive -> "positive"
Negative -> "negative"
Neutral -> "neutral"
showGender :: Gender -> String
showGender g = case g of
Male -> "male"
Female -> "female"
Any -> "male,female"
And finally you can just wrap your base API's comments function so that the filters parameter is more typesafe, and it's converted to the (String, String) form internally to send the request
comments :: String -> [FilterKey] -> Config -> Result
comments name filters conf = do
let filterPairs = map filterKeyToPair filters
commentsRaw name filterPairs conf
This will work quite well and is fairly easy to use:
comments "tide-pods" [Arbitrary "limits" "3", Sentiment Positive, Gender Female] config
But it isn't very extensible. If a user of your library wants to extend it to add a Limit Int field, they would have to write it as
data Limit = Limit Int
limitToFilterKey :: Limit -> FilterKey
limitToFilterKey (Limit l) = Arbitrary "limit" (show l)
And it would instead look like
[limitToFilterKey (Limit 3), Sentiment Positive, Gender Female]
which isn't particularly nice, especially if they're trying to add a lot of different fields and types. A complex but extensible solution would be to have a single Filter type, and actually for simplicity have it capable of representing a single filter or a list of filters (try implementing it where Filter = Filter [(String, String)], it's a bit harder to do cleanly):
import Data.Monoid hiding (Any)
-- Set up the filter part of the API
data Filter
= Filter (String, String)
| Filters [(String, String)]
deriving (Eq, Show)
instance Monoid Filter where
mempty = Filters []
(Filter f) `mappend` (Filter g) = Filters [f, g]
(Filter f) `mappend` (Filters gs) = Filters (f : gs)
(Filters fs) `mappend` (Filter g) = Filters (fs ++ [g])
(Filters fs) `mappend` (Filters gs) = Filters (fs ++ gs)
Then have a class to represent the conversion to a Filter (much like Data.Aeson.ToJSON):
class FilterKey kv where
keyToString :: kv -> String
valToString :: kv -> String
toFilter :: kv -> Filter
toFilter kv = Filter (keyToString kv, valToString kv)
The instance for Filter is quite simple
instance FilterKey Filter where
-- Unsafe because it doesn't match the Fitlers contructor
-- but I never said this was a fully fleshed out API
keyToString (Filter (k, _)) = k
valToString (Filter (_, v)) = v
toFilter = id
A quick trick you can do here to easily combine values of this type is
-- Same fixity as <>
infixr 6 &
(&) :: (FilterKey kv1, FilterKey kv2) => kv1 -> kv2 -> Filter
kv1 & kv2 = toFilter kv1 <> toFilter kv2
Then you can write instances of the FilterKey class that work with:
data Arbitrary = Arbitrary String String deriving (Eq, Show)
infixr 7 .=
(.=) :: String -> String -> Arbitrary
(.=) = Arbitrary
instance FilterKey Arbitrary where
keyToString (Arbitrary k _) = k
valToString (Arbitrary _ v) = v
data Sentiment
= Positive
| Negative
| Neutral
deriving (Eq, Show, Bounded, Enum)
instance FilterKey Sentiment where
keyToString _ = "sentiment"
valToString Positive = "positive"
valToString Negative = "negative"
valToString Neutral = "neutral"
data Gender
= Male
| Female
| Any
deriving (Eq, Show, Bounded, Enum)
instance FilterKey Gender where
keyToString _ = "gender"
valToString Male = "male"
valToString Female = "female"
valToString Any = "male,female"
Add a bit of sugar:
data Is = Is
is :: Is
is = Is
sentiment :: Is -> Sentiment -> Sentiment
sentiment _ = id
gender :: Is -> Gender -> Gender
gender _ = id
And you can write queries like
example
= comments "tide-pods" config
$ "limit" .= "3"
& sentiment is Positive
& gender is Any
This API can still be safe if you don't export the constructors to Filter and if you don't export toFilter. I left that as a method on the typeclass simply so that Filter can override it with id for efficiency. Then a user of your library simply does
data Limit
= Limit Int
deriving (Eq, Show)
instance FilterKey Limit where
keyToString _ = "limit"
valToString (Limit l) = show l
If they wanted to keep the is style they could use
limit :: Is -> Int -> Limit
limit _ = Limit
And write something like
example
= comments "foo" config
$ limit is 3
& sentiment is Positive
& gender is Female
But that is shown here simply as an example of one way you can make EDSLs in Haskell look very readable.

Related

how to filter sum types in Haskell

for example
data CampingStuff = Apple String Int
| Banana String Int
| Pineapple String Int
| Table String Int
| Chairs String Int
I want to have a query function
pickStuff :: [CampingStuff] -> ??? -> [CampingStuff]
the ??? I want to pass Apple then the pickStuff is going to filter out all stuffs like
Apple "Jane" 3
Apple "Jack" 5
Apple "Lucy" 6
something I can think of is like
pickStuffs stuffs dummyStuff
= filter
(\x ->
(x == dummyStuff)
stuffs
pickStuffs stuffs (Apple "" 0)
instance Eq CampingStuff where
compare (Apple name1 number1) (Apple name2 number2)
= True
the drawback of it is :
passing extra parameters to dummy value is not elegant and is not making any sense "" 0
it has to implement all the value constructor in Eq type class ( Apple, Table , Chair)
it is not scalable as in the future I would like to filter out all the apples from Janes
like this (Apple "Jane" _)
Thank you for reading this and appreciate any help how to filter on this [CampingStuff] by Data Constructor like Apple/Table ?
The problem is that unsaturated constructors can't really be compared by value. The only things you can do with them are invoke them or pattern match on them. So if you want a function that tests for Apple, it'll have to be totally different from a function that tests for Banana - they can't share any code, because they have to compare against a different set of patterns.
This is all much easier if you refactor your type to remove the obvious duplication, leaving you with saturated value constructors. The generated Eq instance is all you'll need for comparing types:
data StuffType = Apple | Banana | Pineapple | Table | Chairs deriving Eq
data CampingStuff = Stuff { stuffType :: StuffType
, owner :: String
, quantity :: Int
}
Then you can easily write a function of type CampingStuff -> Bool by composing a couple functions.
hasType :: StuffType -> CampingStuff -> Bool
hasType t s = stuffType s == t
and use that to filter a list:
pickStuff :: StuffType -> [CampingStuff] -> [CampingStuff]
pickStuff = filter . hasType
In the comments, you ask: What if my constructors weren't all uniform, so I couldn't extract everything out to a product type with an enum in it?
I argue that, in such a case, you won't be happy with the result of a pickStuff function no matter how it's implemented. Let's imagine a simpler type:
data Color = Red | Green
data Light = Off | On Color
Now, you might wish to filter a [Light] such that it includes only lights that are On, regardless of their color. Fine, we can implement that. We won't even worry about generalizing, because the type is so small:
ons :: [Light] -> [Light]
ons = filter on
where on Off = False
on (On _) = True
Now you have lights :: [Light], and you can get onLights = ons lights :: [Light]. Amazing. What will you do with onLights next? Perhaps you want to count how many of each color there are:
import qualified Data.Map as M
colorCounts :: [Light] -> M.Map Color Int
colorCounts = M.fromListWith (+) . map getColor
where getColor (On c) = (c, 1)
colorCounts has a problem: it assumes all the lights are On, but there's no guarantee of that in the type system. So you can accidentally call colorCounts ls instead of colorCounts (ons ls), and it will compile, but give you an error at runtime.
Better would be to just do your pattern matching at the point when you'll know what to do with the results. Here, that's inside of colorCounts: just add a case for Off, and use mapMaybe instead of map so you have a chance to throw away values you don't like:
colorCounts' :: [Light] -> M.Map Color Int
colorCounts' = M.fromListWith (+) . mapMabye getColor
where getColor (On c) = Just (c, 1)
getColor Off = Nothing
The same arguments all hold for more complicated types: don't pattern match on a value until you're ready to handle all the information you might find.
Of course, one way to handle such information is to put it into a new type that contains only the information you want. So you could very well write a function
colorsOfOnLights :: [Light] -> [Color]
colorsOfOnLights = mapMaybe go
where go Off = Nothing
go (On c) = Just c
This way, you can't possibly mix up the input of the "filter" function with the output: the output is clearly divorced from the original Light type, and its values can only have come from the On lights. You can do the same thing for your CampingStuff type by extracting a new product type for each of the constructors:
data CampingStuff = Apple AppleData
| Banana BananaData
-- ...
data AppleData = AppleData String Int Bool
data BananaData = BananaData String Int
-- ...
asApple :: CampingStuff -> Maybe AppleData
asApple (Apple ad) = Just ad
asApple _ = Nothing
apples :: [CampingStuff] -> [AppleData]
apples = mapMaybe asApple
You'll need separate functions for asApple and for asBanana and so on. This seems cumbersome, and I don't exactly disagree, but in practice people don't really need large numbers of functions like this. It's usually better to do as I described before: delay the pattern match until you know what to do with the results.
For the function you want to have, you could create functions such as
isApple :: CampingStuff -> Bool
isApple Apple{} = True
isApple _ = False
and then use filter isApple. When you want to filter by Jane, you add another 5 functions for each type, like isAppleFrom :: String -> CampingStuff -> Bool and do filter (isAppleFrom "Jane").
Another approach is the following:
data StuffType = AppleT | BananaT | PineappleT | TableT | ChairsT deriving Eq
data Query = ByStuff StuffType | ByName String deriving Eq
pickStuff :: [CampingStuff] -> [Query] -> [CampingStuff]
pickStuff xs qs = filter cond xs
where
cond :: CampingStuff -> Bool
cond x = all (\q -> case (q, x) of
(ByStuff AppleT, Apple{}) -> True
...other pairs...
(ByName name1, Apple name2 _) -> name1 == name2
...
_ -> False) qs
That is, separate querying from the data types. The above is an example and may be written better.

instance declaration and convert string to Maybe type

An guessing and answering game.
Each Card comprises a Suit, one of A,B,C,D,E,F,G, and a Rank, one of 1|2|3.
I need to have a function to guarantee input(String) whether is valid type. And also write an instance declaration so that the type is in the Show class.
I am not sure about the function toCard, how to express "String" in the function and meet it with the condition.
data Suit = A|B|C|D|E|F|G
deriving (Show , Eq)
data Rank = R1|R2|R3
deriving (Show, Eq)
data Card = Card Suit Rank
deriving (Show, Eq)
instance Show Card where show (Card a b)
= show a ++ show (tail show b)
toCard :: String -> Maybe Card
toCard all#(x:xs)
| (x=A|B|C|D|E|F)&&(xs==1|2|3) = Just Card
| otherwise = Nothing
edit toCard function, input should be any String, so i use a list expression, but it seems to be not correct, cause i try it in ghci, (x=A|B|C|D|E|F)&&(y==1|2|3) is not valid
1) Firstly,
instance Show Card where show (Card a b)
= show a ++ show (tail show b)
You have automatically derived a Show instance for Card, so this will conflict (you can only have 1 instance), and further it won't compile. The show should go on a new line, and tail should be applied to the result of show b.
instance Show Card where
show (Card a b) = show a ++ " " + tail (show b)
2) Secondly,
toCard :: String -> Maybe Card
toCard all#(x:xs)
| (x=A|B|C|D|E|F)&&(xs==1|2|3) = Just Card
| otherwise = Nothing
The syntax (x=A|B|C|D|E|F)&&(xs==1|2|3) is pretty wild, and certainly not valid Haskell. The closest approximation would be something like, x `elem` ['A','B','C','D','E','F'] && xs `elem` ["1","2","3"], but as you can see this rapidly becomes boilerplate-y. Also, Just Card makes no sense - you still need to use the x and xs to say what the card actually is! eg. Just $ Card x xs (though that still won't work because they're still character/string not Suit/Rank).
One solution would be to automatically derive a Read instance on Rank, Suit, and Card. However, the automatic derivation for read on Card would require you to input eg. "Card A R1", so let's try using the instance on Rank and Suit to let us write a parser for Cards that doesn't require prefixed "Card".
First attempt:
toCard :: String -> Maybe Card
toCard (x:xs) = Just $ Card (read [x] :: Suit) (read xs :: Rank)
Hmm, this doesn't really allow us to deal with bad inputs - problem being that read just throws errors instead of giving us a Maybe. Notice however that we use [x] rather than x because read applies to [Char] and x :: Char. Next attempt:
import Text.Read (readMaybe)
toCard :: String -> Maybe Card
toCard [] = Nothing
toCard (x:xs) = let mSuit = readMaybe [x] :: Suit
mRank = readMaybe xs :: Rank
in case (mSuit, mRank) of
(Just s, Just r) -> Just $ Card s r
_ -> Nothing
This copes better with bad inputs, but has started to get quite long. Here's two possible ways to shorten it again:
-- using the Maybe monad
toCard (x:xs) = do mSuit <- readMaybe [x]
mRank <- readMaybe xs
return $ Card mSuit mRank
-- using Applicative
toCard (x:xs) = Card <$> readMaybe [x] <*> readMaybe xs
A parser library, though it comes with a steeper learning curve, makes this simpler. For this example, we'll use Text.Parsec, from the parsec library. Import it, and define a type alias for using in defining your parsers.
import Text.Parsec
type Parser = Parsec String () -- boilerplate
Parsec String () indicates a type of parser that consumes a stream of characters, and can produce a value of type () after parsing is complete. (In this case, we only care about the parsing, not any computation done along side the parsing.)
For your core types, we'll define a Show instant by hand for Rank so that you don't need to strip the R off later. We'll also derive a Read instance for Suit to make it easier to convert a string like "A" to a Suit value like A.
data Suit = A|B|C|D|E|F|G deriving (Show, Read, Eq)
data Rank = R1|R2|R3 deriving (Eq)
-- Define Show yourself so you don't constantly have to remove the `R`
instance Show Rank where
show R1 = "1"
show R2 = "2"
show R3 = "3"
data Card = Card Suit Rank deriving Eq
instance Show Card where
show (Card s r) = show s ++ show r
With that out of the way, we can define some parsers for each type.
-- Because oneOf will fail if you don't get one
-- of the valid suit characters, read [s] is guaranteed
-- to succeed. Using read eliminates the need for a
-- large case statement like in rank, below.
suit :: Parser Suit
suit = do
s <- oneOf "ABCDEF"
return $ read [s]
rank :: Parser Rank
rank = do
n <- oneOf "123"
return $ case n of
'1' -> R1
'2' -> R2
'3' -> R3
-- A Card parser just combines the Suit and Rank parsers
card :: Parser Card
card = Card <$> suit <*> rank
-- parse returns an Either ParseError Card value;
-- you can ignore the exact error if the parser fails
-- to return Nothing instead.
toCard :: String -> Maybe Card
toCard s = case parse card "" s of
Left _ -> Nothing
Right c -> Just c
oneOf is a predefined parser that consumes exactly one of the items in the given list.
parse takes three arguments:
A parser
A "source name" (only used for error message; you can use any string you like)
The string to parse

Confused about custom data types in Haskell

The task: I am trying to create a custom data type and have it able to print to the console. I also want to be able to sort it using Haskell's natural ordering.
The issue: Write now, I can't get this code to compile. It throws the following error: No instance for (Show Person) arising from a use of 'print'.
What I have so far:
-- Omitted working selection-sort function
selection_sort_ord :: (Ord a) => [a] -> [a]
selection_sort_ord xs = selection_sort (<) xs
data Person = Person {
first_name :: String,
last_name :: String,
age :: Int }
main :: IO ()
main = print $ print_person (Person "Paul" "Bouchon" 21)
You need a Show instance to convert the type to a printable representation (a String). The easiest way to obtain one is to add
deriving Show
to the type definition.
data Person = Person {
first_name :: String,
last_name :: String,
age :: Int }
deriving (Eq, Ord, Show)
to get the most often needed instances.
If you want a different Ord instance, as suggested in the comments, instead of deriving that (keep deriving Eq and Show unless you want different behaviour for those), provide an instance like
instance Ord Person where
compare p1 p2 = case compare (age p1) (age p2) of
EQ -> case compare (last_name p1) (last_name p2) of
EQ -> compare (first_name p1) (first_name p2)
other -> other
unequal -> unequal
or use pattern matching in the definition of compare if you prefer,
compare (Person first1 last1 age1) (Person first2 last2 age2) =
case compare age1 age2 of
EQ -> case compare last1 last2 of
EQ -> compare first1 first2
other -> other
unequal -> unequal
That compares according to age first, then last name, and finally, if needed, first name.

Sort by constructor ignoring (part of) value

Suppose I have
data Foo = A String Int | B Int
I want to take an xs :: [Foo] and sort it such that all the As are at the beginning, sorted by their strings, but with the ints in the order they appeared in the list, and then have all the Bs at the end, in the same order they appeared.
In particular, I want to create a new list containg the first A of each string and the first B.
I did this by defining a function taking Foos to (Int, String)s and using sortBy and groupBy.
Is there a cleaner way to do this? Preferably one that generalizes to at least 10 constructors.
Typeable, maybe? Something else that's nicer?
EDIT: This is used for processing a list of Foos that is used elsewhere. There is already an Ord instance which is the normal ordering.
You can use
sortBy (comparing foo)
where foo is a function that extracts the interesting parts into something comparable (e.g. Ints).
In the example, since you want the As sorted by their Strings, a mapping to Int with the desired properties would be too complicated, so we use a compound target type.
foo (A s _) = (0,s)
foo (B _) = (1,"")
would be a possible helper. This is more or less equivalent to Tikhon Jelvis' suggestion, but it leaves space for the natural Ord instance.
To make it easier to build comparison function for ADTs with large number of constructors, you can map values to their constructor index with SYB:
{-# LANGUAGE DeriveDataTypeable #-}
import Data.Generics
data Foo = A String Int | B Int deriving (Show, Eq, Typeable, Data)
cIndex :: Data a => a -> Int
cIndex = constrIndex . toConstr
Example:
*Main Data.Generics> cIndex $ A "foo" 42
1
*Main Data.Generics> cIndex $ B 0
2
Edit:After re-reading your question, I think the best option is to make Foo an instance of Ord. I do not think there is any way to do this automatically that will act the way you want (just using deriving will create different behavior).
Once Foo is an instance of Ord, you can just use sort from Data.List.
In your exact example, you can do something like this:
data Foo = A String Int | B Int deriving (Eq)
instance Ord Foo where
(A _ _) <= (B _) = True
(A s _) <= (A s' _) = s <= s'
(B _) <= (B _) = True
When something is an instance of Ord, it means the data type has some ordering. Once we know how to order something, we can use a bunch of existing functions (like sort) on it and it will behave how you want. Anything in Ord has to be part of Eq, which is what the deriving (Eq) bit does automatically.
You can also derive Ord. However, the behavior will not be exactly what you want--it will order by all of the fields if it has to (e.g. it will put As with the same string in order by their integers).
Further edit: I was thinking about it some more and realized my solution is probably semantically wrong.
An Ord instance is a statement about your whole data type. For example, I'm saying that Bs are always equal with each other when the derived Eq instance says otherwise.
If the data your representing always behaves like this (that is, Bs are all equal and As with the same string are all equal) then an Ord instance makes sense. Otherwise, you should not actually do this.
However, you can do something almost exactly like this: write your own special compare function (Foo -> Foo -> Ordering) that encapsulates exactly what you want to do then use sortBy. This properly codifies that your particular sorting is special rather than the natural ordering of the data type.
You could use some template haskell to fill in the missing transitive cases. The mkTransitiveLt creates the transitive closure of the given cases (if you order them least to greatest). This gives you a working less-than, which can be turned into a function that returns an Ordering.
{-# LANGUAGE TemplateHaskell #-}
import MkTransitiveLt
import Data.List (sortBy)
data Foo = A String Int | B Int | C | D | E deriving(Show)
cmp a b = $(mkTransitiveLt [|
case (a, b) of
(A _ _, B _) -> True
(B _, C) -> True
(C, D) -> True
(D, E) -> True
(A s _, A s' _) -> s < s'
otherwise -> False|])
lt2Ord f a b =
case (f a b, f b a) of
(True, _) -> LT
(_, True) -> GT
otherwise -> EQ
main = print $ sortBy (lt2Ord cmp) [A "Z" 1, A "A" 1, B 1, A "A" 0, C]
Generates:
[A "A" 1,A "A" 0,A "Z" 1,B 1,C]
mkTransitiveLt must be defined in a separate module:
module MkTransitiveLt (mkTransitiveLt)
where
import Language.Haskell.TH
mkTransitiveLt :: ExpQ -> ExpQ
mkTransitiveLt eq = do
CaseE e ms <- eq
return . CaseE e . reverse . foldl go [] $ ms
where
go ms m#(Match (TupP [a, b]) body decls) = (m:ms) ++
[Match (TupP [x, b]) body decls | Match (TupP [x, y]) _ _ <- ms, y == a]
go ms m = m:ms

Haskell data structures oddity

I've been attempting to write a small file to try out a bag-like data structure. So far my code is as follows:
data Fruit = Apple | Banana | Pear deriving (Eq, Show)
data Bag a = EmptyBag | Contents [(a, Integer)]
emptyBag :: Bag a
emptyBag = EmptyBag
unwrap :: [a] -> a
unwrap [x] = x
isObject theObject (obj, inte) = theObject == obj
count :: Bag a -> a -> Integer
count (Contents [xs]) theObject = snd (unwrap (filter (isObject theObject) [xs]))
count EmptyBag _ = 0
But when I try and run it I get the error
Could not deduce (Eq a) from the context ()
arising from a use of 'isObject' at ....
Whereas when I take the count function out and call
snd(unwrap(filter (isObject Banana) [(Apple,1),(Banana,2)]))
it happily returns 2.
Any clues on why this is, or advice on writing this kind of data structure would be much appreciated.
(==) can only be used in a context that includes Eq, but when you declared count you didn't include that context. If I'm reading correctly, that would be
count :: Eq a => Bag a -> a -> Integer
If you declare count without including the type, you can ask ghci for the inferred type; or just ask for the inferred type of snd (unwrap (filter (isObject Banana) [(Apple,1),(Banana,2)]))

Resources