Преобразовать String в Integer/Float в Haskell?
data GroceryItem = CartItem ItemName Price Quantity | StockItem ItemName Price Quantity
makeGroceryItem :: String -> Float -> Int -> GroceryItem
makeGroceryItem name price quantity = CartItem name price quantity
Я хочу создать GroceryItem при использовании String или [String]
createGroceryItem :: [String] -> GroceryItem
createGroceryItem (a:b:c) = makeGroceryItem a b c
Вход будет в формате [ "Apple", "15.00", "5" ], который я сломал, используя функцию слов в haskell. Я получаю эту ошибку, которая, по моему мнению, связана с тем, что makeGroceryItem принимает Float и Int. Но как мне сделать b и c Float и 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*
Ответы
Ответ 1
read
может анализировать строку в float и int:
Prelude> :set +t
Prelude> read "123.456" :: Float
123.456
it :: Float
Prelude> read "123456" :: Int
123456
it :: Int
Но проблема (1) находится в вашем шаблоне:
createGroceryItem (a:b:c) = ...
Здесь :
является (право-ассоциативным) двоичным оператором, который добавляет элемент в список. RHS элемента должен быть списком. Поэтому, учитывая выражение a:b:c
, Haskell выведет следующие типы:
a :: String
b :: String
c :: [String]
то есть. c
будет рассматриваться как список строк. Очевидно, что он не может быть read
или передан в любые функции, ожидающие String.
Вместо этого вы должны использовать
createGroceryItem [a, b, c] = ...
если список должен иметь ровно 3 элемента или
createGroceryItem (a:b:c:xs) = ...
если допустимо ≥3.
Также (2) выражение
makeGroceryItem a read b read c
будет интерпретироваться как makeGroceryItem
, используя 5 аргументов, 2 из которых являются функцией read
. Вам нужно использовать скобки:
makeGroceryItem a (read b) (read c)
Ответ 2
Несмотря на то, что это уже сделано, я настоятельно рекомендую использовать reads
для преобразования строк, потому что это намного безопаснее, поскольку оно не прерывается с неисправимым исключением.
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)]
[]
В случае успеха он вернет список только с одним элементом и кортеж, состоящий из преобразованного значения и, возможно, неконвертируемых дополнительных символов, а при сбое - пустой список.
Это легко сопоставить шаблон для успеха и неудачи, и он не будет взорваться в вашем лице!
Ответ 3
Две вещи:
createGroceryItem [a, b, c] = makeGroceryItem a (parse b) (parse c)
-- pattern match error if not exactly 3 items in list
или, альтернативно,
createGroceryItem (a : b : c : _) = makeGroceryItem a (parse b) (parse c)
-- pattern match error if fewer than 3 items in list, ignore excess items
поскольку :
не совпадает с ++
.
Между тем с правой стороны --- сторона, которая дает вам сообщение об ошибке, которое вы видите --- вам нужно группировать выражения с помощью скобок. В противном случае parse
интерпретируется как значение, которое вы хотите передать на makeGroceryItem
, поэтому компилятор жалуется, когда вы пытаетесь передать 5 аргументов функции, которая принимает только 3 параметра.
Ответ 4
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)
- > печатает 16.0
Вот как я решил эту задачу в своем проекте.