2015-12-07 4 views
5

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

isDigit :: Char -> Bool 
isDigit c = c >= '0' && c <= '9' 

parseNumber :: String -> Maybe (String, String) 
parseNumber [] = Just ("", "") 
parseNumber (h:ls) 
    | isDigit h 
     | p == Nothing = Just([h], ls)  -- Digit found <<< ERROR!! 
     | otherwise = Just (h:fst d, snd d) -- Ends in a digit 
    | h == '.' 
     | p == Nothing = Nothing        -- Ends in a point 
     | not ('.' `elem` (snd d)) = Just (h:(fst d), snd d) -- We don't want multiple dots 
    | otherwise = Nothing  -- Not a number, stop looking! 
    where 
     p = parseNumber ls 
     Just d = parseNumber ls -- Float version of p. Not used if p is Nothing 

Эта функция должна принимать строку, начинающуюся с номером, и возвращает число, отделенный от остальной части выражения. Пример:

parseNumber "123,0 + 2"

("123,0", "+ 2")

Я думаю, что синтаксис этих вложенных охранников читает очень хорошо, но это не делает Работа. Погрешность для отмеченной строки:

Ошибка синтаксического разбора при вводе `| '

Прицепные ограждения не допускаются в Haskell? Или я так ошибаюсь? Кроме того, какие альтернативы мне нужно для простой логики цепи?

ответ

11

Нет, но вы можете использовать случаи, если вы хотите:

parseNumber :: String -> Maybe (String, String) 
parseNumber [] = Just ("", "") 
parseNumber (h:ls) 
    | isDigit h = 
     case() of 
      () | p == Nothing = Just([h], ls) 
       | otherwise = Just (h:fst d, snd d) -- Ends in a digit 
    | h == '.' = 
     case() of 
      () | p == Nothing = Nothing 
       | not ('.' `elem` (snd d)) = Just (h:(fst d), snd d) 
    | otherwise = Nothing 
    where 
     p  = parseNumber ls 
     Just d = parseNumber ls 

В качестве альтернативы, если многоходовой работает в аналогичным образом (if True | p1 -> b ; | p2 -> c).

3

Нет, это невозможно. Почему бы просто не написать его линейно

isDigit :: Char -> Bool 
isDigit c = c >= '0' && c <= '9' 

parseNumber :: String -> Maybe (String, String) 
parseNumber [] = Just ("", "") 
parseNumber (h:ls) 
    -- Digit found 
    | isDigit h && p == Nothing = Just([h], ls) 
    -- Ends in a digit 
    | isDigit h = Just (h:fst d, snd d) 
    -- Ends in a point 
    | h == '.' && p == Nothing = Nothing 
    -- We don't want multiple dots 
    | h == '.' && not ('.' `elem` (snd d)) = Just (h:(fst d), snd d) 
    -- Not a number, stop looking! 
    | otherwise = Nothing 
    where 
     p = parseNumber ls 
     Just d = parseNumber ls -- Float version of p. Not used if p is Nothing 

main = print $ parseNumber "123.0 + 2" 

Если охранники становятся слишком вовлечены это, вероятно, признак того, что вам нужно извлечь функцию.

+0

Спасибо, он отлично работает. Мне просто немного легче читать. – SlySherZ

5

Когда функция становится чрезвычайно сложной, и вы не можете поддерживать логику, которая осуществляется только с оберегает в одиночку, рассмотрим написание функции с абстрактными функциями управления вместо:

import Control.Applicative 
import Control.Monad 

isDigit :: Char -> Bool 
isDigit c = c >= '0' && c <= '9' 

parseNumber :: String -> Maybe (String, String) 
parseNumber [] = return ("", "") 
parseNumber (h:ls) = dig <|> dot where -- h is either a digit or a dot 
    p = parseNumber ls 
    dig = do 
    guard (isDigit h)         -- ensure h is a digit 
    fmap (\(ds,r) -> (h:ds,r)) p 
     <|> return ([h],ls) -- the alternative between two computations 
          -- either the tail is parsed and h prepended to the result 
          -- or the digit is returned by itself       
    dot = do 
    guard (h == '.')    -- ensure h is a dot 
    (ds,r) <- p     -- parse the tail 
    guard $ not $ '.' `elem` ds -- ensure there is no dot in the tail 
    return (h:ds,r)    -- result 

Это использует Monad, Functor и MonadPlus экземпляры Maybe для реализации логики синтаксического анализа. Фактически эта функция обобщается на тип MonadPlus m => String -> m (String, String) - здесь нет фактического использования конструкторов Maybe.

Функция также легко читается. Гораздо более очевидно, что происходит в версии с охранниками.

+0

Это подход, который я хотел бы предложить. Есть одно замечание, которое, я думаю, стоит сделать в теле ответа, хотя: в этом случае преобразование в 'dig <|> dot' в порядке, потому что' dig' и 'dot' начинаются с взаимоисключающих охранников, но в целом они не могут и то проваливание от 'dig' до' dot' может быть нежелательным. Это можно обрабатывать с помощью многострочной if или 'case() _' конструкции, хотя синтаксис немного неуклюжий, когда он не нужен (например, здесь). –

+0

@ DanielWagner Даже если охранники, где не взаимоисключающие, один из них будет первым, поэтому преобразование его в 'guard1 <|> guard2' по-прежнему кажется мне верным. – user2407038

+0

Если охранники * не * взаимно исключают друг друга, а 'p' терпят неудачу, но' dot' преуспевает, 'dig <|> dot' преуспеет там, где оригинал потерпел бы неудачу, не попробовав' dot'. –

3

Это is можно связать ограждения, с ,. Это в основном то же самое, что и && в ответе fjarri, но он более универсален, когда дело доходит до шаблонов.

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

parseNumber (h:ls) 
    | isDigit h 
     = if isNothing p 
      then Just ([h], ls)  -- Digit found <<< ERROR!! 
      else Just (h:fst d, snd d) -- Ends in a digit 
    | h == '.' 
    , not ('.' `elem` snd d) 
     = Just (h:fst d, snd d) -- We don't want multiple dots 
    | otherwise = Nothing  -- Not a number, stop looking! 
1

Использование where Just d = ... опасно: если вы когда-либо доступ к нему, когда p это Nothing вся программа зависнет.Делая это, вы должны добавить такие проверки в свой код (как вы это уже сделали), и будьте осторожны, чтобы не забыть ни один из них.

Есть более безопасные способы, такие как использование case p of Nothing -> ... ; Just d -> ..., с помощью выпрямителя maybe или с использованием инструментов-инструментов/аппликативных/монадов. Давайте использовать case держать его просто:

parseNumber :: String -> Maybe (String, String) 
parseNumber [] = Just ("", "") 
parseNumber (h:ls) 
    | isDigit h = case p of 
     Nothing -> Just([h], ls)   -- Digit found <<< ERROR!! 
     Just d -> Just (h:fst d, snd d) -- Ends in a digit 
    | h == '.' = case p of 
     Nothing -> Nothing     -- Ends in a point 
     Just d | not ('.' `elem` (snd d)) 
       -> Just (h:(fst d), snd d) -- We don't want multiple dots 
     _  -> Nothing     -- Not a number, stop looking! 
    where 
     p = parseNumber ls 

Мы также можем непосредственно шаблон матч на субкомпонентах d:

parseNumber :: String -> Maybe (String, String) 
parseNumber [] = Just ("", "") 
parseNumber (h:ls) 
    | isDigit h = case p of 
     Nothing  -> Just([h], ls)  -- Digit found <<< ERROR!! 
     Just (hs,rest) -> Just (h:hs, rest) -- Ends in a digit 
    | h == '.' = case p of 
     Nothing -> Nothing   -- Ends in a point 
     Just (hs, rest) | not ('.' `elem` rest) 
       -> Just (h:hs, rest) -- We don't want multiple dots 
     _  -> Nothing   -- Not a number, stop looking! 
    where 
     p = parseNumber ls 
11

Нет, вы не можете. Мы все это хотим, но никто не может придумать разумный синтаксис.

0

Поместите их в разделенные функции.

isDigit :: Char -> Bool 
isDigit c = c >= '0' && c <= '9' 


parseNumber :: String -> Maybe (String, String) 
parseNumber [] = Just ("", "") 
parseNumber (h:ls) 
    | isDigit h = f_p (h:ls)    
    | h == '.' = temp (h: ls)   
    | otherwise = Nothing  -- Not a number, stop looking! 


f_p :: String -> Maybe (String, String) 
f_p (h:ls) 
    | parseNumber ls == Nothing = Just([h], ls)   -- Digit found <<< ERROR!! 
    | otherwise     = Just (h:fst d, snd d) -- Ends in a digit 
    where 
     Just d = parseNumber ls -- Float version of p. Not used if p is Nothing 


temp :: String -> Maybe (String, String) 
temp (h:ls) 
    | parseNumber ls == Nothing = Nothing     -- Ends in a point 
    | not ('.' `elem` (snd d)) = Just (h:(fst d), snd d) -- We don't want multiple dots 
    where 
     Just d = parseNumber ls -- Float version of p. Not used if p is Nothing 

Должен признаться, что я не тестировал этот код.

1

Последние GHC теперь MultiWayIf:

{-# LANGUAGE MultiWayIf #-} 

parseNumber :: String -> Maybe (String, String) 
parseNumber [] = Just ("", "") 
parseNumber (h:ls) 
    | isDigit h = if 
    | p == Nothing -> Just ([h], ls) 
    | otherwise -> Just (h:fst d, snd d) 
    | h == '.' = if 
    | p == Nothing    -> Nothing 
    | not ('.' `elem` (snd d)) -> Just (h:(fst d), snd d) 
    | otherwise = Nothing 
    where [email protected](~(Just d)) = parseNumber ls 

Но это лучше немного написано по-разному, так или иначе, без пристрастности.

{-# LANGUAGE MultiWayIf #-} 

parseNumber :: String -> Maybe (String, String) 
parseNumber [] = Just ("", "") 
parseNumber (h:ls) 
    | isDigit h = if 
    | Nothing <- p -> Just ([h], ls) -- PatternGuards, on by default 
    | Just d <- p -> Just (h:fst d, snd d) 
    | h == '.' = if 
    | Nothing <- p       -> Nothing 
    | Just d <- p, not ('.' `elem` snd d) -> Just (h:(fst d), snd d) 
    | otherwise = Nothing 
    where p = parseNumber ls 

а также вы можете использовать maybe.

parseNumber :: String -> Maybe (String, String) 
parseNumber "" = Just ("", "") 
parseNumber (h:hs) 
    | isDigit h = maybe (Just ([h], hs)) (\(num, rest') -> Just (h:num, rest')) rest 
    | h == '.' = maybe Nothing (\(num, rest') -> if '.' `elem` num then Nothing 
               else Just (h:num, rest') 
          ) rest -- This logic is a bit wonky; it doesn't really work 
    | otherwise = Nothing 
    where rest = parseNumber hs 
Смежные вопросы