2015-12-07 15 views
5

ここで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")

私は、このネストされた警備員構文は本当にうまく読み込みと思いますが、そうでありません作業。 マークされた行のエラーが読み込まれます。

入力時に解析エラーが発生しました。

ハスケルではチェーンガードは許可されていませんか?それとも何とかこれを間違って書いていますか?また、どのような選択肢を単純な方法でロジックをチェーン化する必要がありますか?

答えて

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 

これはMonadFunctor、およびMonadPlusを使用しています構文解析ロジックを実装するためのMaybeのインスタンス。実際、この関数はタイプMonadPlus m => String -> m (String, String)に一般化されています - ここにはMaybeコンストラクタの実際の使用はありません。

この機能は読みやすいです。ガード付きのバージョンでは何が起こっているかははるかに明らかです。

+0

これは私が提案したいアプローチです。しかし、答えのボディには価値があると思うことがあります:この場合、 'dig <|> dot 'への変換は' dig'と 'dot'が相互に排他的なガードで始まるので大丈夫ですが、 'dig'から' dot'に落ちることは望ましくないかもしれません。これは、複数のif(case()of _)構文で扱うことができますが、構文は必要ないときには少し不器用です(ここのように)。 –

+0

@DanielWagnerもし互いに排他的ではない場所にいるガードであっても、そのうちの1つが最初に来るので、それを 'guard1 <|> guard2'に変換することはまだ有効です。 – user2407038

+0

ガードが相互に排他的ではなく、 'p'が失敗したのに' dot'が成功した場合、 'dig <|> dot'は' dot'を試みることなくオリジナルが失敗したところで成功します。 –

3

であり、,でチェーンガード可能です。これはfjarriの回答では基本的に&&と同じですが、パターンガードについてはもっと多目的です。

ガードは入れられません。最初の節で実際に必要なのはあなたの例です。 pは、あなたのプログラム全体がクラッシュするNothingあるときに、あなたが今までそれにアクセスする場合:あなたは危険ですwhere Just d = ...を使用して

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

を書くことができます。これを行うことで、コードにそのようなチェックを追加しなければなりません(すでに正しく行ったように)、これらのいずれかを忘れないように注意してください。

を使用するか、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 

このコードはテストしていません。

{-# 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 

しかし、これはえこひいきなしで、とにかく少し異なる書かれた方が良い:

1

最近のGHCは今MultiWayIfを持っています。

{-# 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 
関連する問題