Pattern match on data types - haskell

I have been struggling with this problem so far and was wondering if someone could point me in the right direction. This is my template so far:
data Expense = MkExpense Double String deriving (Eq,Ord)
mkExpense :: String -> Double -> Expense
mkExpense name amount = MkExpense amount name
instance Show Expense where
show (MkExpense amount name) = name ++ ": " ++ show amount
data Delta = MkDelta Expense deriving (Eq,Ord)
instance Show Delta where
show (MkDelta (MkExpense amount name)) = name ++ ": " ++ show amount
Right now I would like to sum over all the amounts of a list of the form (so in this case I would like to get 240):
[mkExpense "Alex" 40, mkExpense "Gert-Jan" 200]
which returns:
[Alex: 40.0,Gert-Jan: 200.0]
I know of the existence of foldr but I'm not sure how to pattern match properly against the "amounts" here. Normally I would use map to re-map to a single value list so I can use foldr freely, but it's not possible here because of my definition of the show-instance I presume.
Any help is appreciated.

You don't need map; the function supplied to foldr can pattern-match directly.
> foldr (\(MkExpense v _) t -> v + t) 0 [mkExpense "Alex" 40, mkExpense "Gert-Jan" 200]
240.0
The same technique can be used for composing sum and map:
> sum $ map (\(MkExpense v _) -> v) [mkExpense "Alex" 40, mkExpense "Gert-Jan" 200]
240.0

map (\(MkExpense amount name) -> amount) [mkExpense "Alex" 40, mkExpense "Gert-Jan" 200]
does the trick, having "two constructors" where one flips the arguments caused the confusion as to why my map wasn't working.

Related

error Couldn't match expected type ‘Char’ with actual type ‘[Char]’

I am trying to build a string representation for the show function of a typeclass representing a polynomial. I keep getting type errors of a mismatch from 'Char' to '[Char]', but from my understanding haskell's "append" function should be able to concatenate a Char to a string/[Char]. I don't understand where the problem lies, or where to look for a solution based on the errors I receive. here is the faulty code:
newtype Poly a = P [a]
instance (Num a, Show a) => Show (Poly a) where
show p = ["" : form (p !! i) i | i <- [l,(l-1)..0]]
where
l = length p
form e i
| i == 0 = elem
| i == 1 = elem ++ "x + "
| otherwise = elem ++ "x^" ++ (show i) ++ " + "
where elem = show e
any help would be greatly appreciated, thanks in advance.
You write
from my understanding haskell's "append" function should be able to concatenate a Char to a string/[Char].
I have no idea where you got this idea. It's wrong. I'm guessing you've defined
type Poly a = [a]
and I'll go with that assumption.
instance (Num a, Show a) => Show (Poly a) where
This is wrong. Poly is a type synonym. You can only declare instances for proper first-class types (the application of a type constructor to zero or more type variables). You can fix this by using, instead,
newtype Poly a = Poly {getPoly :: [a]}
but then you need to wrap/unwrap the Poly data constructor as required. Once you've gotten this right, you'll probably see that the Num constraint you've given is unnecessary.
show p = ["" ++ form (p !! i) i | i <- [(length p)..0]]
There are a few problems. The big one is that this does not define a string (list of characters) but rather a list of strings. You can fix this, generally, by applying concat to the result. The second one is that "" ++ anything is just anything, because concatenating the empty list to another list doesn't do anything. The third problem is that you're trying to count down, but you've done it wrong. That notation only counts up. To count down, you have to show that you want to count down:
let lp = length p in [lp, (lp-1) .. 0]
The last thing I see immediately (some of these mistakes are repeated in the preceding two lines):
| otherwise = e ++ "x^" ++ i ++ " + "
Now i is an Int, and ++ only works for lists. So that will not work. You need to first convert i to a string using show. e is of type a, and needs to be converted to a string using show as well.

get element of datatype with id in haskell

Say I have two datatypes look like this:
data DataType1 = DataType1 { id :: Int, values :: [Int]} deriving (Show)
data DataType2 = DataType2 { dataType1Id :: Int, values2 :: [Int]} deriving (Show)
Now I want to compare the average of values and the related values2 (so where DataType1Id = id).
I am pretty new in Haskell so I don't know if this is possible.
This is my function to compare the averages:
isGreaterAvgThen :: DataType2 -> Bool
isGreaterAvgThen x
| average (values2 x) > average (values y) = True
| otherwise = False
where y = ...
average is a function that calculates the average of a list of Ints (average :: [Int] -> Double)
Would this work for you?
where y = fromMaybe (error "missing value") $
find ((== id x) . dataType1Id) listOfDataType2
Since the list might be missing the desired value, the type of find is wrapped in Maybe.... If you are sure that it is there, you can unwrap it with fromMaybe like I did, but be warned, error is just another way to say "crash"....

Sum the total balance for people between two ages

I'm trying to calculate the total balance of people/clients between two ages that have an account within a bank. I can get it to display the list of people that fall under the requirement.
These are the constructors
type NI = Int
type Age = Int
type Balance = Int
type Person = (NI, Age, Balance)
type Bank = [Person]
This is the Bank
rbs :: Bank
rbs = [ (1, 73, 1000)
, (2, 18, -50)
, (3, 60, 190)
, (4, 26, 300)
, (5, 24, 456)
, (6, 32, 7500)
, (7, 41, -46)
, (8, 59, -850)
, (9, 44, 348)
, (10, 66, -1000)
, (11, 37, 20000)
, (12, 29, -245)
, (13, 55, 3090)
]
And this is my code to recursively check the bank
equityA' :: Bank -> (Int, Int) -> Bank
equityA' ((n,a,b):xs) (0,0) = error "No ages were selected"
equityA' [] (x,y) = []
equityA' ((n,a,b):xs) (f, s) = if (f <= a) && (s >= a) then (n,a,b) : equityA' xs (f, s)
else equityA' xs (f, s)
If I run equityA' rbs (40,50) the output will be [(7,41,-46),(9,44,348)]
What i'm struggling to do is to is print out the total balance of those people. I have some code down but am stuck at the actual calculation part.
The code for to check the total.
equityAge :: Bank -> (Int, Int) -> Int
equityAge ((n,a,b):xs) (0,0) = error "No ages were selected"
equityAge [] (x,y) = 0
equityAge ((n,a,b):xs) (f, s) =
I would be grateful for any help.
It's quite simple if you use list comprehensions:
equityAge :: Bank -> (Int,Int) -> Int
equityAge _ (0,0) = error "No ages were selected"
equityAge [] (x,y) = 0
equityAge x (f,s) = sum [ b | (n,a,b) <-x, f <= a, a <= s]
Other than that you can apply Sarah's advice and use map and sum on your original function:
equityAge :: Bank -> (Int,Int) -> Int
equityAge _ (0,0) = error "No ages were selected"
equityAge [] (x,y) = 0
equityAge x (f,s) = sum (map(\(_,_,b) -> b) (equityA' x (f,s)))
There are three main things I would recommend here: (a) use record types instead of tuples; (b) use standard utility functions to work with lists and other standard types; (c) write auxiliary functions to help keep all of you functions short and readable.
On the first recommendation:
data Person = Person { ni :: NI, age :: Age, balance :: Balance } deriving (Eq, Show)
Now you don't have to do any of that tuple pattern matching just to get a person's age; this record type declaration gets you functions age :: Person -> Age and balance :: Person -> Balance for free.
Example of the third recommendation: here is a function to calculate whether a Person is between two ages:
betweenAges :: Age -> Age -> Person -> Bool
betweenAges lo hi person = lo <= age person && age person <= hi
Now, using that auxiliary function and the standard filter function, you can get all the persons between two ages like this:
filter (betweenAges 18 27) rbs
To get the balance of a list of Persons you can just use the standard sum and map functions:
sum (map balance somePersons)
So the complete solution would be something like this:
-- Note how much easier to read this is when you use sum, map, filter
-- and betweenAges!
equityAge :: Age -> Age -> Bank -> Balance
equityAge lo hi bank = sum (map balance (filter (betweenAges lo hi) bank))
-- This could be its own top-level function if you're going to reuse it
-- somewhere else, but here I'm putting it in a where declaration assuming
-- that it's a throwaway.
where betweenAges lo hi person = lo <= age person && age person <= hi
As a general rule, laboring away to figure out how to solve the issue you're tackling is not productive. The more productive strategy, I believe, is the following: (a) learn to solve your problems in terms of the functions in the basic libraries like Prelude, Data.List and Data.Maybe; (b) learn how to write those utility functions on your own. Why this way? Because (a) teaches you how to break problems down into small, reusable pieces, and (b) teaches you to understand what's going on from the bottom to the top in these programs.

Removing String double-quotes in Haskell

This function generates simple .dot files for visualizing automata transition functions using Graphviz. It's primary purpose is debugging large sets of automatically generated transitions (e.g., the inflections of Latin verbs).
prepGraph :: ( ... ) => NFA c b a -> [String]
prepGraph nfa = "digraph finite_state_machine {"
: wrapSp "rankdir = LR"
: wrapSp ("node [shape = circle]" ++ (mapSp (states nfa \\ terminal nfa)))
: wrapSp ("node [shape = doublecircle]" ++ (mapSp $ terminal nfa))
: formatGraph nfa ++ ["}"]
formatGraph :: ( ... ) => NFA c b a -> [String]
formatGraph = map formatDelta . deltaTuples
where formatDelta (a, a', bc) = wrapSp (mkArrow a a' ++ " " ++ mkLabel bc)
mkArrow x y = show x ++ " -> " ++ show y
mkLabel (y, z) = case z of
(Just t) -> "[ label = \"(" ++ show y ++ ", " ++ show t ++ ")\" ]"
Nothing -> "[ label = \"(" ++ show y ++ ", " ++ "Null" ++ ")\" ]"
where wrap, wrapSp and mapSp are formatting functions, as is deltaTuples.
The problem is that formatGraph retains double quotes around Strings, which causes errors in Graphviz. E.g., when I print unlines $ prepGraph to a file, I get things like:
0 -> 1 [ label = "('a', "N. SF")" ];
instead of
0 -> 1 [ label = "('a', N. SF)" ];
(However, "Null" seems to work fine, and outputs perfectly well). Now of course the string "N. SF" isn't the actual form I use to store inflections, but that form does include a String or two. So how can I tell Haskell: when you show a String values, don't double-quote it?
Check out how Martin Erwig handled the same problem in Data.Graph.Inductive.Graphviz:
http://hackage.haskell.org/packages/archive/fgl/5.4.2.3/doc/html/src/Data-Graph-Inductive-Graphviz.html
The function you're looking for is "sq" at the bottom:
sq :: String -> String
sq s#[c] = s
sq ('"':s) | last s == '"' = init s
| otherwise = s
sq ('\'':s) | last s == '\'' = init s
| otherwise = s
sq s = s
(check out the context and adapt for your own code, of course)
Use dotgen package - it has special safeguards in place to prevent forbidden chars from sneaking into attribute values.
You could define your own typeClass like this:
class GShow a where
gShow :: a -> String
gShow = show
instance GShow String where
show = id
instance GShow Integer
instance GShow Char
-- And so on for all the types you need.
The default implementation for "gShow" is "show", so you don't need a "where" clause for every instance. But you do need all the instances, which is a bit of a drag.
Alternatively you could use overlapping instances. I think (although I haven't tried it) that this will let you replace the list of instances using the default "gShow" by a single line:
instance (Show a) => GShow a
The idea is that with overlapping instances the compiler will chose the most specific instance available. So for strings it will pick the string instance over the more general one, and for everything else the general one is the only one that matches.
It seems a little ugly, but you could apply a filter to show t
filter (/='"') (show t)

Haskell: writing the result of a computation to file

I have a function which creates a tuple after computation, but I would like to write it to file.
I know how to write to a file using writeFile, but do not know how to combine the computation and monads IO together in the type signature.
This is my code.
invest :: ([Char]->Int->Int->([Char], Int) )
-> [Char]->Int->Int->([Char], Int)
invest myinvest x y = myinvest x y
myinvest :: [Char]->Int->Int->([Char], Int)
myinvest w x y
| y > 0 = (w, x + y)
| otherwise = error "Invest amount must greater than zero"
where
I have a function which computes the maximum value from a list, but I want this function to receive input from a file, and then perform the computation of maximum value.
maximuminvest :: (Ord a) => [a] -> a
maximuminvest [] = error "Empty Invest Amount List"
maximuminvest [x] = x
maximuminvest (x:xs)
| x > maxTail = x
| otherwise = maxTail
where maxTail = maximuminvest xs
Please help.
Thanks.
[Edit]
My new question is at below.
The first and second question can be solve through function composition but when i try it say type mismatch.
I have check it but i couldn't find any errors.
invest :: ( [Char]->Int->Int->([Char], Int) ) -> [Char]->Int->Int-> ([Char], Int)
invest theinvest x y = theinvest x y
theinvest :: [Char]->Int->Int-> ([Char], Int)
theinvest w x y | y > 0 = (w, x + y)
| otherwise = error "Invest amount must greater than zero"
savefile :: ([Char], Int) -> IO()
savefile (x, y) = do
let name = fst (x, y)
let temp = snd(x, y)
let amount = show temp
writeFile "C:\\Invest.txt" (name ++ " " ++ amount)
test = savefile . theinvest "asd" 1234 234
The error message is
ERROR - Type error in application
* Expression : savefile . invest theinvest "234" 234 234
Term : invest theinvest "234" 234 234
Type : ([Char],Int)
* Does not match : a -> b
Please help. My return type is ([Char],Int). Why it complaint as a -> b ? Thanks
I solve this using command like savefile (invest theinvest "asd" 12 12) but why the operator doesn;t works ?
My fourth question is i have something like this ["peter","1000","michell","2000","kelly","3000"] and i would like to convert to [("peter",1000),("michell", 2000),("kelly",3000)]
The reading of file content is ok but i want to filter the string and get the number only. For instance, a file that has "peter 100\nasd 200"
I would like to drop the alphabet and remain the integer here.
I just want the [100, 200] to be the argument the function.
Please help.
Thanks.
You might want to do something like
main = do
c <- computation
writeFile "filename" (show c)
to write out to the file using a Show instance for the result of the computation. If your types are easy enough, this is both human-readable and readable for Haskell restoring the value again.
For the second question, suppose your file stores values as
[1.5,2.3,5.1,6.3,9.8]
then it is blindingly easy to read them in and perform a computation:
main = do
str <- readFile "filename"
return $ computation (read str)
should do it. If instead you have your data with an item per line, or in a CSV file, or something else, it gets a little bit more involved. For CSV there is Text.CSV up on Hackage that seems to do the trick.
For your question about funcion composition, you have to remember that function application binds very strongly - more strongly than composition. So your problematic expression parses as
savefile . (theinvest "asd" 1234 234)
The type of (theinvest "asd" 1234 234) is ([Char],Int) which is not a function and can't be composed. That's what the type error is about.
You want to apply savefile to this, and the easiest is to just remove the . and put the parentheses in. Another way is to replace the . by $ which is a weakly binding function application operator. And if you really, really want a period in there, you could use (savefile . theinvest "asd" 1234) 234 but that's very silly and unclear, in my opinion.
creates a tuple after computation, but I would like to write it to file.
The simplest way is with 'show', which gives a text-based serialization method for most Haskell data types.
writeFile "foo" (show c)
For more efficient serialization, there is Data.Binary:
encodeFile "foo" c
Which will write it in a binary format.
for your last question you would use lines to get ["peter 2000", Joe "50"] etc. then use filter to drop non numerics.
so
filter Data.Char.isDigit . lines
should do it(Note: code written without being tested may not be 100%, and it doesn't handle the case of "7Bill 400" correctly.)

Resources