2016-03-31 4 views
4

Как вы можете видеть, я написал программу, например:Haskell, та же программа с использованием монады

тест "12 124 212" = Right [12, 124, 212]

тест «43 243 fs3d 2" = левый "fs3d не число"

test :: String -> Either String [Int] 
test w = iter [] $ words w 
    where 
     iter acc []  = Right (reverse acc) 
     iter acc (x:xs) = if (all isDigit x) then 
           iter ((read x):acc) xs 
          else 
           Left (x++ "is not a number") 

ловит обучение монады. Не могли бы вы показать мне, как реализовать его, используя монады?

ответ

4

Я думаю, что вы ищете traverse/mapM (они одинаковы для списков). Также вы можете использовать readEither для упрощения:

import Data.Traversable (traverse) 
import Data.Bifunctor (first) 
import Text.Read (readEither) 

test :: String -> Either String [Int] 
test = traverse parseItem . words 

parseItem :: String -> Either String Int 
parseItem x = first (const $ x++" is not a number") $ readEither x 

Так что же mapM делать? Он в основном реализует рекурсию над списком, который вы сделали вручную. Однако, в отличии от стандартной map функции он принимает монадическую функцию (parseItem в нашем случае, когда Either String является монадой) и применяет один шаг в списке за другие: ответ

iter [] = Right [] 
iter (x:xs) = do 
    r <- parseItem x 
    rs <- iter xs 
    return (r:rs) 
+0

Почему вы aplicate два аргумента для readEither? Требуется только один аргумент (String) –

+1

@HaskellFun: Спасибо. Я как-то ожидал, что он примет сообщение об ошибке и строку, подлежащую анализу. Исправлено сейчас, используя [удивительные бифунтеры] (http://hackage.haskell.org/package/base-4.8.2.0/docs/Data-Bifunctor.html#v:first) :-) – Bergi

+0

Это не работает как мой soltuion. Он возвращает ошибку разбора без неправильной строки –

2

BERĢI является только правом, но, возможно, вы найдете, что это легко понять, представлены следующим образом:

test :: String -> Either String [Int] 
test str = traverse parseNumber (words str) 

parseNumber :: String -> Either String Int 
parseNumber str 
    | all isDigit str = Right (read str) 
    | otherwise  = Left (str ++ " is not a number") 

другая вещь, которую я рекомендовал бы это не писать хвостовой рекурсией аккумулятор петли, как iter в вашем примере. Вместо этого просмотрите библиотечную документацию и попробуйте найти функции списка, которые делают то, что вы хотите. В этом случае, как правильно указал Берги, traverse - это именно то, что вы хотите. Тем не менее, потребуется некоторое исследование, чтобы полностью освоить эту функцию. Но, учитывая, как Monad экземпляр Either и Traversable экземпляр списков работы, traverse в этом примере как это работает:

-- This is the same as `traverse` for lists and `Either` 
traverseListWithEither :: (a -> Either err b) -> [a] -> Either err [b] 
traverseListWithEither f [] = Right [] 
traverseListWithEither f (a:as) = 
    case f a of 
     Left err -> Left err 
     Right b -> mapEither (b:) (traverseListWithEither f as) 

-- This is the same as the `fmap` function for `Either`  
mapEither :: (a -> b) -> Either e a -> Either e b 
mapEither f (Left e) = Left e 
mapEither f (Right a) = Right (f a) 
Смежные вопросы