Reputation: 19872
data GroceryItem = CartItem ItemName Price Quantity | StockItem ItemName Price Quantity
makeGroceryItem :: String -> Float -> Int -> GroceryItem
makeGroceryItem name price quantity = CartItem name price quantity
I want to create a `GroceryItem` when using a `String` or `[String]`
createGroceryItem :: [String] -> GroceryItem
createGroceryItem (a:b:c) = makeGroceryItem a b c
The input will be in the format ["Apple","15.00","5"]
which I broke up using Haskell's words
function.
I get the following error which I think is because makeGroceryItem
accepts a Float
and an Int
.
*Type error in application
*** Expression : makeGroceryItem a read b read c
*** Term : makeGroceryItem
*** Type : String -> Float -> Int -> GroceryItem
*** Does not match : a -> b -> c -> d -> e -> f*
But how do I make b
and c
of type Float
and Int
, respectively?
Upvotes: 79
Views: 112724
Reputation: 34061
readMaybe
can be used for this. It is also a total function as opposed to read
(which could throw an exception).
Prelude> import Text.Read
Prelude Text.Read> readMaybe ("1.5") :: Maybe Float
Just 1.5
Upvotes: 0
Reputation: 5722
Even though this question already has an answer, I strongly suggest using reads
for string conversion, because it's much safer, as it does not fail with an unrecoverable exception.
reads :: (Read a) => String -> [(a, String)]
Prelude> reads "5" :: [(Double, String)]
[(5.0,"")]
Prelude> reads "5ds" :: [(Double, String)]
[(5.0,"ds")]
Prelude> reads "dffd" :: [(Double, String)]
[]
On success, reads
returns a list with exactly one element: A tuple consisting of the converted value and maybe unconvertable extra characters. On failure, reads
returns an empty list.
It's easy to pattern-match on success and failure, and it will not blow up in your face!
Upvotes: 81
Reputation: 1043
filterNumberFromString :: String -> String
filterNumberFromString s =
let allowedString = ['0'..'9'] ++ ['.', ',']
toPoint n
| n == ',' = '.'
| otherwise = n
f = filter (`elem` allowedString) s
d = map toPoint f
in d
convertStringToFloat :: String -> Float
convertStringToFloat s =
let betterString = filterNumberFromString s
asFloat = read betterString :: Float
in asFloat
print (convertStringToFloat "15,00" + 1)
-> prints 16.0
Thats how I solved this task in my project.
Upvotes: 0
Reputation: 523714
read
can parse a string into float and int:
Prelude> :set +t
Prelude> read "123.456" :: Float
123.456
it :: Float
Prelude> read "123456" :: Int
123456
it :: Int
But the problem (1) is in your pattern:
createGroceryItem (a:b:c) = ...
Here :
is a (right-associative) binary operator which prepends an element to a list. The RHS of an element must be a list. Therefore, given the expression a:b:c
, Haskell will infer the following types:
a :: String
b :: String
c :: [String]
i.e. c
will be thought as a list of strings. Obviously it can't be read
or passed into any functions expecting a String.
Instead you should use
createGroceryItem [a, b, c] = ...
if the list must have exactly 3 items, or
createGroceryItem (a:b:c:xs) = ...
if ≥3 items is acceptable.
Also (2), the expression
makeGroceryItem a read b read c
will be interpreted as makeGroceryItem
taking 5 arguments, 2 of which are the read
function. You need to use parenthesis:
makeGroceryItem a (read b) (read c)
Upvotes: 100
Reputation: 47062
Two things:
createGroceryItem [a, b, c] = makeGroceryItem a (parse b) (parse c)
-- pattern match error if not exactly 3 items in list
or alternatively
createGroceryItem (a : b : c : _) = makeGroceryItem a (parse b) (parse c)
-- pattern match error if fewer than 3 items in list, ignore excess items
because :
is not the same as ++
.
Meanwhile on the right hand side --- the side that's giving you the error message you see --- you have to group expressions using brackets. Otherwise parse
is interpreted as being a value you want to pass to makeGroceryItem
, so the compiler complains when you try to pass 5 arguments to a function that only takes 3 parameters.
Upvotes: 5