I'm still very much trying to get into haskell, but I've noticed something that annoyed me quite a bit.
In the book "Learn You a Haskell for Great Good!" there's this part that shows the use of guards in pattern matching, in the case of the book it was a small function that calculates the bmi of a person, it went a little something like this (parts changed slightly to not infringe copyright or whatever):
bmiCalc :: (RealFloat a) => a -> a -> String
bmiCalc weight height
| bmi <= 18.5 = "skinny"
| bmi <= 25.0 = "normal"
| bmi <= 30.0 = "fat"
| otherwise = "obese"
where bmi = weight / height ^ 2
That's all fine and dandy the code works as advertised, but I thought, what if it also showed what the bmi it calculated was along with the text?
So I re-wrote the code to this:
bmiCalc :: (RealFloat a) => a -> a -> String
bmiCalc weight height
| bmi <= 18.5 = "skinny, " ++ show bmi
| bmi <= 25.0 = "normal, " ++ show bmi
| bmi <= 30.0 = "fat, " ++ show bmi
| otherwise = "obese, " ++ show bmi
where bmi = weight / height ^ 2
Expecting "show" to work like .toString does in java and c#
Boy was I wrong.
ghci gave me this big nasty error message:
Could not deduce (Show a) arising from a use of `show'
from the context (RealFloat a)
bound by the type signature for
bmiCalc :: RealFloat a => a -> a -> String
at file.hs:1:16-48
Possible fix:
add (Show a) to the context of
the type signature for bmiCalc :: RealFloat a => a -> a -> String
In the second argument of `(++)', namely `show bmi'
In the expression: "skinny, " ++ show bmi
In an equation for `bmiCalc':
bmiCalc weight height
| bmi <= 18.5 = "skinny, " ++ show bmi
| bmi <= 25.0 = "normal, " ++ show bmi
| bmi <= 30.0 = "fat, " ++ show bmi
| otherwise = "obese, " ++ show bmi
where
bmi = weight / height ^ 2
Failed, modules loaded: none.
why is that? why doesn't it allow me to append what appears to return a string, to a string? I mean as far as I've understood "skinny, " ++ show bmi is a string... which is exactly what the type signature says I have to return
so what did I do wrong here?
Change the type signature to:
bmiCalc :: (RealFloat a, Show a) => a -> a -> String
Because you want to use the member function show, from the Show typeclass; but you haven't specified that in the function constraint, and ghci has no way to infer that to be correct.
RealFloat isn't a showable type. You'll have to add a show constraint.
Related
I am new to Haskell and am trying to write a function that does the following:
asks user to enter "Youth" "Adult" or "Senior" and indicate of the one they pick
function then calculates total price of tickets (Y = $10.50, A = $20, S = $15)
Sample output:
Enter type of ticket and count:
Senior 7
Total price is: 95.00
Here is what I have come up with so far:
calcPrice :: (String a) => a -> b -> b
calcPrice x y =
if x == "Youth"
then "Total price :" ++ 10.5 * y
else if x == "Adult"
then "Total price :" ++ 20 * y
else if x == "Senior"
then "Total price :" ++ 15 * y
else "Invalid input."
...but I get this error:
homework8.hs:31:15: error:
• Expected kind ‘* -> Constraint’, but ‘String’ has kind ‘*’
• In the type signature: calcPrice :: (String a) => a -> b -> b
|
31 | calcPrice :: (String a) => a -> b -> b |
Any help is appreciated, but my main questions are:
How do we specify the types if we want to output a string and a number?
In other variants, an error message appeared that it was not happy about the comparison in the if/else if statements. why?
How do I make the function output something if it can only be called with arguments that the user has no idea to input until the message is displayed?
EDIT_________________
Here is the new code after making the changes you all have mentioned:
calcPrice :: (Integral a) => String -> a -> String
calcPrice x y =
if x == "Youth"
then "Total price :" ++ Show(10.5 * y)
else if x == "Adult"
then "Total price :" ++ Show(20 * y)
else if x == "Senior"
then "Total price :" ++ Show(15 * y)
else "Invalid input."
and I now get this error...
homework8.hs:34:31: error:
• Data constructor not in scope: Show :: a -> [Char]
• Perhaps you meant variable ‘show’ (imported from Prelude)
|
34 | then "Total price :" ++ Show(10.5 * y) | ^^^^
homework8.hs:36:31: error:
• Data constructor not in scope: Show :: a -> [Char]
• Perhaps you meant variable ‘show’ (imported from Prelude)
|
36 | then "Total price :" ++ Show(20 * y) | ^^^^
homework8.hs:38:31: error:
• Data constructor not in scope: Show :: a -> [Char]
• Perhaps you meant variable ‘show’ (imported from Prelude)
|
38 | then "Total price :" ++ Show(15 * y) | ^^^^
Failed, no modules loaded.
How do we specify the types if we want to output a string and a number?
One way is to give back a tuple of a string and a number:
calcPrice :: String -> Double -> (String, Double)
calcPrice x y =
if x == "Youth"
then ("Total price :", 10.5 * y)
else if x == "Adult"
then ("Total price :", 20 * y)
else if x == "Senior"
then ("Total price :", 15 * y)
else ("Invalid input.", 0/0)
However, as the comments pointed out, in this case it's probably more sensible to encode and append the number part into the string. Conversion from many types to String is easily done with the show function (which is a method of the Show (uppercase) typeclass – note how the show function you use in your value level code is lowercase)
calcPrice :: String -> Double -> String
calcPrice x y =
if x == "Youth"
then "Total price :" ++ show (10.5 * y)
else if x == "Adult"
then "Total price :" ++ show (20 * y)
else if x == "Senior"
then "Total price :" ++ show (15 * y)
else "Invalid input."
The other sub-questions are unclear to me – maybe remove them here and flesh them out as separate questions.
Some stylistic remarks:
Rather than doing a bunch of separate if x== statements, it's neater and in general also more efficient to use case (which is very flexible in Haskell, and often works even for types that can't be equality-compared):
calcPrice x y = case x of
"Youth" -> "Total price :" ++ show (10.5 * y)
"Adult" -> "Total price :" ++ show (20 * y)
"Senior" -> "Total price :" ++ show (15 * y)
_ -> "Invalid input."
Alternative syntax for the same thing: you can simply write a bunch of separate function clauses for specific values of x, rather than only one that accepts any x
calcPrice "Youth" y = "Total price :" ++ show (10.5 * y)
calcPrice "Adult" y = "Total price :" ++ show (20 * y)
calcPrice "Senior" y = "Total price :" ++ show (15 * y)
calcPrice _ _ = "Invalid input."
Not just in Haskell, but any language it's good to follow the DRY principle. A standard way of this is to define local variables, like
calcPrice x y = case x of
"Youth" -> totalPrice ++ show (10.5 * y)
"Adult" -> totalPrice ++ show (20 * y)
"Senior" -> totalPrice ++ show (15 * y)
_ -> "Invalid input."
where totalPrice = "Total price :"
don't do it this way, because the actual code-duplication (++ show, * y) is still there! Instead, define local functions (which are also variables)
calcPrice x y = case x of
"Youth" -> priceMul 10.5
"Adult" -> priceMul 20
"Senior" -> priceMul 15
_ -> "Invalid input."
where priceMul μ = "Total price :" ++ show (μ*y)
In a bigger project you should probably separate the “is the input valid?” from the actual calculation logic, else you'll soon have weird errors popping up deep in your code where you never expected them. Worse, if the error is merely “signalled” by a dedicated string value "Invalid input", it becomes very difficult to find where the error did happen.
Advocates of dynamic languages argue that this is what unit tests are there for, but a static type system reduces the need for that a lot. Specifically, you can accept instead of a string which could easily have lots of invalid values, a type that only has those values that are actually valid:
data PricingTier = Youth | Adult | Senior
calcPrice :: PricingTier -> Double -> String
calcPrice x y = case x of
Youth -> priceMul 10.5
Adult -> priceMul 20
Senior -> priceMul 15
where priceMul μ = "Total price :" ++ show (μ*y)
I'm going through Learn You A Haskell. I have a following function:
bmiTell :: (RealFloat a) => a -> a-> String
bmiTell weight height
| bmi <= skinny ="underweight"
| bmi <= normal = "ok"
| bmi <= fat = "fat"
| otherwise = "whale"
where
bmi = weight/height^2
(skinny,normal,fat)=(18.5, 25.0,30.0)
which works perfectly fine. I'm now making a list comprehension, where
a desired result is something like this:
[(68, "underweight"),(69,"ok"),(70,"ok")]
this is my ghci input:
[(x,y)| x <-[68..70], y <- bmiTell x 185]
and the output is
[(68.0,'u'),(68.0,'n'),(68.0,'d'),(68.0,'e'),(68.0,'r'),(68.0,'w'),(68.0,'e'),(68.0,'i'),(68.0,'g'),(68.0,'h'),(68.0,'t'),(69.0,'u'),(69.0,'n'),(69.0,'d'),(69.0,'e'),(69.0,'r'),(69.0,'w'),(69.0,'e'),(69.0,'i'),(69.0,'g'),(69.0,'h'),(69.0,'t'),(70.0,'u'),(70.0,'n'),(70.0,'d'),(70.0,'e'),(70.0,'r'),(70.0,'w'),(70.0,'e'),(70.0,'i'),(70.0,'g'),(70.0,'h'),(70.0,'t')]
I tried making it a (x,[y]), but I get the same result with Chars in ""s instead of single quotes
You can simply use the map function:
map (\w -> (w, bmiTell w 185)) [68..70]
Or as #ErikR mentioned, using list comprehension:
[ (x, bmiTell x 185) | x <- [68..70] ]
How to derive a parameterized similarity in a way that it would be convenient to use in Haskell?
The class should be such that the domain can be numeric or text (and possibly something else), and the parameter controlling the internals of comparison function can also be of several types.
Below, you may find the one approach that uses two class parameters. What implications this design entails if the goal is to define several "similarity or equality groups"? (What kind of use cases would be hard to implement compared to some alternative implementation?) In this example, the similarity groups of words could be defined to be edit distances of one, two etc. and in double to be different precisions.
Some of the methods take both numeric and textual inputs like the "quiteSimilar"-method. Why not use just some distance? Some of the similarities should be able to be defined by the user of the parameterized equality, e.g. on text (words) they could be based on synonyms.
And on doubles, well, I don't know yet, what kind of comparisons will be needed. (Suggestions are welcome.) After equalities comes the question, how to compare the order of items so that similar items will be deemed to be equal and not the larger and smaller, see the last line of the output.
{-# LANGUAGE MultiParamTypeClasses #-}
import Data.Array
import qualified Data.Text as T
-- parameterized eq
class Peq a b where peq :: a -> b -> b -> Bool
instance Peq Double Double where peq = almostEqRelPrec
instance Peq Int T.Text where peq = editDistance
class Comment a where
quiteSimilar :: a -> a -> T.Text
instance Comment Double where
quiteSimilar a b = if peq (epsilon * 100::Double) a b then T.pack "alike" else T.pack "unalike"
instance Comment T.Text where
quiteSimilar a b = if peq (1::Int) a b then T.pack "alike" else T.pack "unalike"
x1' x = quiteSimilar 0.25 (0.25 - x * epsilon :: Double)
x1 = quiteSimilar 0.25 (0.25 - 25 * epsilon :: Double)
x2 = quiteSimilar 0.25 (0.25 - 26 * epsilon :: Double)
x3' x = quiteSimilar 1e12 (1e12 - x * ulp 1e12 :: Double)
x3 = quiteSimilar 1e12 (1e12 - 181 * ulp 1e12 :: Double)
x4 = quiteSimilar 1e12 (1e12 - 182 * ulp 1e12 :: Double)
u181 = 181 * ulp 1e12 :: Double
main = do
let a = 0.2 + 0.65 :: Double
b = 0.85 :: Double
s = T.pack "trial"
t = T.pack "tr1al"
putStrLn $ "0.2 + 0.65 = " ++ show a ++ " and compared to " ++ show b ++ ", it is " ++ T.unpack (quiteSimilar a b)
putStrLn $ "Texts " ++ T.unpack s ++ " and " ++ T.unpack t ++ " are " ++ T.unpack (quiteSimilar s t)
putStrLn $ "Note that " ++ show a ++ " > " ++ show b ++ " is " ++ show (a > b)
-- packege Numeric.Limits contains this one
epsilon :: RealFloat a => a
epsilon = r
where r = 1 - encodeFloat (m-1) e
(m, e) = decodeFloat (1 `asTypeOf` r)
ulp :: RealFloat a => a -> a
ulp a = r
where r = a - encodeFloat (m-1) e
(m, e) = decodeFloat (a `asTypeOf` r)
almostEqRelPrec :: (RealFloat a) => a -> a -> a -> Bool
almostEqRelPrec maxRelPrec a b = d <= (largest * maxRelPrec)
where
d = abs $ a - b
largest = max (abs a) (abs b)
editDistance :: Int -> T.Text -> T.Text -> Bool
editDistance i a b = i == editDistance' (show a) (show b)
-- from https://wiki.haskell.org/Edit_distance
-- see also https://hackage.haskell.org/package/edit-distance-0.2.2.1
editDistance' :: Eq a => [a] -> [a] -> Int
editDistance' xs ys = table ! (m,n)
where
(m,n) = (length xs, length ys)
x = array (1,m) (zip [1..] xs)
y = array (1,n) (zip [1..] ys)
table :: Array (Int,Int) Int
table = array bnds [(ij, dist ij) | ij <- range bnds]
bnds = ((0,0),(m,n))
dist (0,j) = j
dist (i,0) = i
dist (i,j) = minimum [table ! (i-1,j) + 1, table ! (i,j-1) + 1,
if x ! i == y ! j then table ! (i-1,j-1) else 1 + table ! (i-1,j-1)]
On my machine, the output is:
0.2 + 0.65 = 0.8500000000000001 and compared to 0.85, it is alike
Texts trial and tr1al are alike
Note that 0.8500000000000001 > 0.85 is True
Edit:
Trying to rephrase the question: could this be achieved more elegantly with a similarity class that has only one parameter a and not two (a and b)? I have a feeling that multiparameter classes may turn out to be difficult later on. Is this a needless fear? First solution along this line that came to my mind was to define similarity class with one parameter a and a class for functions having two parameters. And on instances constraint other type to be similarity class parameter and the other would be for actual method returning Bool.
Are there some benefits of using the latter approach to the one presented? Or actually what are the possible trade-offs between these approaches? And if there are still more ways to make achieve this kind of things, how do they compare?
could this be achieved more elegantly with a similarity class that has only one parameter a and not two (a and b)
Yes. Many MultiParamTypeClasses can be rewritten quite easily to single-param ones... by simply degrading the second parameter to an associated type family:
{-# LANGUAGE TypeFamilies #-}
class Peq b where
type SimilarityThreshold b :: *
peq :: SimilarityThreshold b -> b -> b -> Bool
instance Peq Double where
type SimilarityThreshold Double = Double
peq = almostEqRelPrec
instance Peq T.Text where
type SimilarityThreshold T.Text = Int
peq = editDistance
This is quite a bit more verbose, but indeed I tend to favour this style. The main difference is that the associated type family always assigng each type of values to be compared unambiguously a threshold-type. This can save you some could not deduce... type inference trouble, however it also means that you can't use two different metric-types for a single type (but why would you, anyway).
Note that you can achieve exactly the same semantics by simply adding a fundep to your original class:
{-# LANGUAGE FunctionalDependencies #-}
class Peq a b | b -> a where
peq :: a -> b -> b -> Bool
This is just a bit different in usage – again I tend to favour the type families approach: it is more explicit in what the parameters are for, while at the same time avoiding the second parameter to turn up in the constraints to any Peq-polymorphic function.
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.
I write a file named "baby.hs" with the following codes
bmiTell :: => Double -> String
bmiTell bmi
| bmi <= 1 = "small"
| bmi <= 10 = "medium"
| bmi <= 100 = "large"
| otherwise = "huge"
When I load this file in GHCi, it complains like this:
ghci>:l baby.hs
[1 of 1] Compiling Main ( baby.hs, interpreted )
baby.hs:1:12: parse error on input ‘=>’
Failed, modules loaded: none.
ghci>
If I remove the =>, it doesn't work either:
bmiTell :: Double -> String
bmiTell bmi
| bmi <= 1 = "small"
| bmi <= 10 = "medium"
| bmi <= 100 = "large"
| otherwise "huge"
Error info:
ghci>:l baby
[1 of 1] Compiling Main ( baby.hs, interpreted )
baby.hs:7:1:
parse error (possibly incorrect indentation or mismatched brackets)
Failed, modules loaded: none.
Does anyone have ideas about this?
In your first case, your type signature is wrong. It should be like this:
bmiTell :: Double -> String -- Notice that there is no =>
In your second case, you are missing = in the last line. It should be like this:
| otherwise = "huge" -- Notice the presence of =
So a proper working code will look like this:
bmiTell :: Double -> String
bmiTell bmi
| bmi <= 1 = "small"
| bmi <= 10 = "medium"
| bmi <= 100 = "large"
| otherwise = "huge"