Преобразовать строку в целое число/плавающее число в Haskell?

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

Входные данные будут в формате ["Apple","15.00","5"], который я разбил с помощью функции Haskell words.

Я получаю следующую ошибку, которая, я думаю, связана с тем, что makeGroceryItem принимает 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*

Но как сделать b и c типа Float и Int соответственно?


person Ranhiru Jude Cooray    schedule 18.03.2010    source источник
comment
у вас интересный проект. для чего это?   -  person Martin Fischer    schedule 09.12.2015


Ответы (5)


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) = ...

Здесь : — это (правоассоциативный) бинарный оператор, который добавляет элемент в начало списка. Правая сторона элемента должна быть списком. Поэтому, учитывая выражение 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)
person kennytm    schedule 18.03.2010
comment
@KennyTM: read "123.456" :: Float . Что означает этот синтаксис? Что здесь ::? Является ли read функцией? - person Nawaz; 30.06.2012
comment
@Nawaz: Да, read — это функция. Выражение f :: T заставляет f иметь тип T. - person kennytm; 30.06.2012
comment
@KennyTM: Итак, синтаксис read "123.456" ::Float примерно эквивалентен sscanf("123.456", "%f", &fnum); в C, верно? - person Nawaz; 30.06.2012
comment
@Nawaz: Только для функции read. См. stackoverflow.com /questions/5926826/ для получения подробной информации. - person kennytm; 30.06.2012
comment
Как на самом деле работает Read? Разбивает ли он строку как список символов Char. Тогда есть ли у него словарь от char до int и сравнение списка char со словарем, чтобы получить соответствующий int? - person CMCDragonkai; 18.12.2014
comment
@CMCDragonkai: он читает все цифры ('0'–'9') и преобразует их в десятичные числа (используя 1234 = 123 × 10 + 4, как и все другие языки). Фактическая реализация довольно сложна, поскольку она также является универсальной для поддержки, например. чтение шестнадцатеричного числа или чтение списка целых чисел. См. hackage.haskell.org/package/base -4.7.0.1/docs/src/GHC-Read.html и hackage.haskell.org/package/base-4.7.0.1/docs/src/, если вам интересно. - person kennytm; 18.12.2014

Несмотря на то, что на этот вопрос уже есть ответ, я настоятельно рекомендую использовать 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)]
[]

В случае успеха reads возвращает список ровно с одним элементом: кортеж, состоящий из преобразованного значения и, возможно, непреобразуемых дополнительных символов. В случае ошибки reads возвращает пустой список.

Легко сопоставить успехи и неудачи, и это не взорвется у вас перед носом!

person LukeN    schedule 21.03.2010
comment
Отличное предложение! Каков ваш предпочтительный способ извлечения результирующего элемента из списка, возвращаемого при чтении? Два fst звонка? - person Alex; 11.10.2015
comment
Начиная с base-4.6 существует readMaybe :: Read a => String -> Maybe a в Text.Read, что удобнее, чем использование reads в данном случае. - person sjakobi; 29.12.2016

Две вещи:

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 параметра.

person dave4420    schedule 18.03.2010

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

Вот как я решил эту задачу в своем проекте.

person chrisheyn    schedule 09.08.2017

readMaybe можно использовать для этого. Это также функция total, в отличие от read (которая может вызвать исключение).

Prelude> import Text.Read
Prelude Text.Read> readMaybe ("1.5") :: Maybe Float
Just 1.5
person Chris Stryczynski    schedule 28.03.2021