Заполнение списка с пользовательского ввода


Я пытаюсь узнать, мне на Haskell, потому что я обнаружил, что это интересный язык. Прямо сейчас, я пытаюсь реализовать функцию в Haskell, чтобы познакомиться с языком. Функция просто получает ввод пользователя и добавляет вводимых чисел в список поплавки.

get_budget_values _values _index _length = do
    tmp_value <- getLine

    let new_value = read tmp_value :: Float
    let new_values = new_value : _values
    let new_index = _index + 1

    if new_index < _length
        then get_budget_values new_values new_index _length
        else return new_values

Итак, является ли это правильный способ реализовать функцию, как это в Haskell? Какие ошибки начинающих делаю я? Существуют ли стандартные способы сделать что-то подобное?



147
2
задан 8 марта 2018 в 07:03 Источник Поделиться
Комментарии
2 ответа

Добавить подписи типа топ-уровня функции

Вы всегда должны добавить подпись типа топ-уровня функции:

get_budget_values :: [Float] -> Int -> Int -> IO [Float]
get_budget_values _values _index _length = do
...

Делает функции легкая в использовании и трудно злоупотреблять

Однако тип этой функции-это странно. Мы хотим сделать _length значения, так почему мы должны поставлять им в первую очередь? Это может привести к ошибкам. Наши функции должны иметь тип Int -> IO [Float] вместо:

get_budget_values :: Int -> IO [Float]
get_budget_values n = go [] 0 n
where
go values index len = do
tmp_value <- getLine

let new_value = read tmp_value
let new_values = new_value : values
let new_index = index + 1

if new_index < len
then go new_values new_index len
else return new_values

Обратите внимание, что в связи с get_budget_valuesтакие подписи мы не должны использовать :: Float в read temp_value больше.

Соблюдать соглашения об именах и использовать существующие библиотечные функции

Все-таки, наша функция-подпаритет, так как мы не следуем общие правила именования (camelCase) С одной стороны не использовать библиотечные функции для Наши преимущества.

Так скажем мы doNTimes :: Int -> IO a -> IO [a] так что doNTimes n action повторяет action n раз и собирает результаты в список. Тогда мы можем написать

getBudgetValues :: Int -> IO [Float]
getBudgetValues n = doNTimes n getSingleFloat
where
getSingleFloat = do
tmpValue <- getLine
return (read tmpValue)

В getLine затем read подход настолько распространен, что есть функция для этого, а именно readLn:

    getSingleFloat = readLn

Мы в конечном итоге с

getBudgetValues :: Int -> IO [Float]
getBudgetValues n = doNTimes n readLn

Не хватает doNTimes. Теперь мы знаем, что есть replicate :: Int -> a -> [a], что почти правильно, но мы бы в конечном итоге с [IO Float] вместо IO [Float]. Но есть также sequence :: [IO a] -> IO [a].*

Таким образом, мы можем написать

doNTimes :: Int -> IO a -> IO [a]
doNTimes n action = sequence (replicate n action)

Конечный результат

Эта функция также часто используется и доступен replicateM в Control.Monad. Мы в конечном итоге с

import Control.Monad (replicateM)

getBudgetValues :: Int -> IO [Float]
getBudgetValues n = replicateM n readLn

Упражнения

Писать replicate и sequence себя, не глядя на их исходный код. replicate :: Int -> a -> [a] следует выполнить следующие свойства для положительных n:

let result = replicate n x
in length result == n && all (== x) result

Для любой не-положительные nсписок должен быть пустым.

sequence :: [IO a] -> IO [a] следует выполнить действия и получить результаты, например

sequence [putStrLn "Hello", putStrLn "World"]

надо напечатать Hello и World на две строки и возврата [(),()].


* фактические типы являются более общими

3
ответ дан 10 марта 2018 в 01:03 Источник Поделиться

Вы просто хотите позвонить getLine функция length_ раз? Это так просто, как

values <- (map read) <$> replicateM length_ getLine :: IO [Float]

Здесь replicateM c a выполняет a действие c раз. В replicateM length_ getLine выражение возвращает действие, которое создает список строк, IO [String]. Используя <$> оператор (он же fmap) можно применить функцию [String] частью этого IO [String] вещь. Наконец, функции мы применяем это map read преобразует String в Float, потому что мы явно написал типа все выражение.

Это может быть полезным для вас, чтобы увидеть, как replicateM само реализуется:

replicateM cnt f =
loop cnt
where
loop cnt = if cnt <= 0 then return [] else do
first <- f
leftover <- loop (cnt - 1)
return (f:leftover)

2
ответ дан 8 марта 2018 в 08:03 Источник Поделиться