Во-первых, я думаю, что у вас есть некоторые базовые проблемы с пониманием Хаскелла, так что давайте пройдемся по шагам.Надеюсь, вы найдете это полезным.Некоторые из них просто дойдут до кода, который у вас есть, а некоторые - нет, но это замедленная версия того, о чем я буду думать, когда писал этот код.После этого я попытаюсь ответить на один ваш конкретный вопрос.
Я не совсем уверен, что вы хотите, чтобы ваша программа делала.Я понимаю, что вам нужна программа, которая читает в качестве входных данных файл, содержащий список людей и их инвестиции.Однако я не уверен, что ты хочешь с этим делать.Кажется, вы (а) хотите иметь разумную структуру данных ([(String,Integer)]
), но затем (б) использовать только целые числа, поэтому я предполагаю, что вы хотите что-то сделать и со строками.Давайте пройдем через это.Во-первых, вам нужна функция, которая может, учитывая список целых чисел, вернуть максимум.Вы называете это maximuminvest
, но эта функция более общая, чем просто инвестиции, так почему бы не назвать ее maximum
?Оказывается, эта функция уже существует.Как ты мог знать это?Я рекомендую Hoogle - это поисковая система Haskell, которая позволяет вам искать как имена, так и типы функций.Вам нужна функция из списков целых чисел в одно целое число, поэтому давайте найдем это .Как оказалось, первый результат - maximum
, который является более общей версией того, что вы хотите.Но для целей обучения, давайте предположим, что вы хотите написать это самостоятельно;в этом случае ваша реализация в порядке.
Хорошо, теперь мы можем вычислить максимум.Но сначала нам нужно составить наш список.Нам понадобится функция типа [String] -> [(String,Integer)]
для преобразования нашего списка без форматирования в разумный.Ну, чтобы получить целое число из строки, нам нужно использовать read
.Короче говоря, ваша текущая реализация этого также хороша, хотя я бы (а) добавил случай error
для списка из одного элемента (или, если мне было приятно, просто вернул пустой список, чтобы игнорироватьпоследний пункт списков нечетной длины), и (б) используйте имя с заглавной буквой, чтобы я мог разделить слова (и, вероятно, другое имя):
tupledInvestors :: [String] -> [(String, Integer)]
tupledInvestors [] = []
tupledInvestors [_] = error "tupledInvestors: Odd-length list"
tupledInvestors (name:amt:rest) = (name, read amt) : tupledInvestors rest
Теперь у нас есть этиМы можем предоставить себе удобную функцию, maxInvestment :: [String] -> Integer
.Единственное, чего не хватает, так это возможности перейти из списка с кортежем в список целых чисел.Есть несколько способов решить эту проблему.Одним из них является тот, который у вас есть, хотя это было бы необычно в ХаскелеСекунду будет использовать map :: (a -> b) -> [a] -> [b]
.Это функция, которая применяет функцию к каждому элементу списка.Таким образом, ваш getint
эквивалентен простому map snd
.Самый хороший способ, вероятно, будет использовать Data.List.maximumBy :: :: (a -> a -> Ordering) -> [a] -> a
.Это похоже на maximum
, но позволяет вам использовать собственную функцию сравнения.И используя Data.Ord.comparing :: Ord a => (b -> a) -> b -> b -> Ordering
, все становится хорошо.Эта функция позволяет сравнивать два произвольных объекта путем преобразования их во что-то, что можно сравнить.Таким образом, я написал бы
maxInvestment :: [String] -> Integer
maxInvestment = maximumBy (comparing snd) . tupledInvestors
Хотя вы также можете написать maxInvestment = maximum . map snd . tupledInvestors
.
Хорошо, теперь перейдем к IO.Таким образом, ваша основная функция хочет читать из определенного файла, вычислять максимальные инвестиции и распечатывать их.Один из способов представить это в виде последовательности из трех отдельных шагов:
main :: IO ()
main = do dataStr <- readFile "C:\\Invest.txt"
let maxInv = maxInvestment $ words dataStr
print maxInv
(Оператор $
, если вы его не видели, это просто приложение функции, но с более удобным приоритетом;имеет тип (a -> b) -> a -> b
, что должно иметь смысл.) Но это let maxInv
кажется довольно бессмысленным, поэтому мы можем избавиться от него:
main :: IO ()
main = do dataStr <- readFile "C:\\Invest.txt"
print . maxInvestment $ words dataStr
.
, если вы его еще не виделивсе же, это функциональная композиция;f . g
совпадает с \x -> f (g x)
.(Он имеет тип (b -> c) -> (a -> b) -> a -> c
, который, если подумать, должен иметь смысл.) Таким образом, f . g $ h x
- это то же самое, что и f (g (h x))
, только легче читать.
Теперь мы смогли избавиться от let
.А как насчет <-
?Для этого мы можем использовать оператор =<< :: Monad m => (a -> m b) -> m a -> m b
.Обратите внимание, что это почти как $
, но с m
портит почти все.Это позволяет нам принять монадическое значение (здесь readFile "C:\\Invest.txt" :: IO String
), передать его функции, которая превращает простое значение в монадическое значение, и получить это монадическое значение.Таким образом, у нас есть
main :: IO ()
main = print . maxInvestment . words =<< readFile "C:\\Invest.txt"
Это должно быть понятно, я надеюсь, особенно если вы думаете о =<<
как о монаде $
.
Я не уверен, что происходит сtestfile
;если вы отредактируете свой вопрос, чтобы отразить это, я постараюсь обновить мой ответ.
Еще одна вещь.Вы сказали, что
Интересно, как мы можем передать входные данные из ввода-вывода монады в другую функцию для выполнения некоторых вычислений?
Как и во всем в Haskell, этовопрос типов .Итак, давайте разберемся с типами здесь.У вас есть некоторая функция f :: a -> b
и некоторое монадическое значение m :: IO a
.Вы хотите использовать f
, чтобы получить значение типа b
.Это невозможно, как я объяснил в моем ответе на другой вопрос ;однако, вы можете получить что-то типа IO b
.Таким образом, вам нужна функция, которая берет ваш f
и дает вам монадическую версию.Другими словами, что-то типа Monad m => (a -> b) -> (m a -> m b)
.Если мы подключим это к Hoogle , первый результат будет Control.Monad.liftM
, который имеет именно эту сигнатуру типа.Таким образом, вы можете трактовать liftM
как несколько другое "монадическое $
", чем =<<
: f \
liftM` m applies
f to the pure result of
m (in accordance with whichever monad you're using) and returns the monadic result. The difference is that
liftM takes a pure function on the left, and
= << `частично-монадический. </p>
Другой способ написать то же самое - использовать do
-обозначение:
do x <- m
return $ f x
Это говорит: "получить x
из m
, применитьf
к нему и поднять результат обратно в монаду. "Это то же самое, что и утверждение return . f =<< m
, которое снова точно равно liftM
.Сначала f
выполняет чистые вычисления;его результат передается в return
(через .
), который поднимает чистое значение в монаду;и затем эта частично-монадическая функция применяется через =<,
к m
.
Уже поздно, поэтому я не уверен, какой смысл это имело.Позвольте мне попытаться подвести итог.Короче говоря, нет общего способа покинуть монаду .Когда вы хотите выполнить вычисления на монадических значениях, вы поднимаете чистые значения (включая функции) в монаду, а не наоборот;это может нарушить чистоту, что было бы Very Bad ™.
Я надеюсь, что на самом деле ответил на ваш вопрос.Дайте мне знать, если это не так, поэтому я могу попытаться сделать его более полезным!