Подтвердить что ты не робот

Как работают исключения в Haskell (часть вторая)?

У меня есть следующий код:

{-# LANGUAGE DeriveDataTypeable #-}
import Prelude hiding (catch)
import Control.Exception (throwIO, Exception)
import Control.Monad (when)
import Data.Maybe
import Data.Word (Word16)
import Data.Typeable (Typeable)
import System.Environment (getArgs)

data ArgumentParserException = WrongArgumentCount | InvalidPortNumber
    deriving (Show, Typeable)

instance Exception ArgumentParserException

data Arguments = Arguments Word16 FilePath String

main = do
    args <- return []
    when (length args /= 3) (throwIO WrongArgumentCount)

    let [portStr, cert, pw] = args
    let portInt = readMaybe portStr :: Maybe Integer
    when (portInt == Nothing) (throwIO InvalidPortNumber)

    let portNum = fromJust portInt
    when (portNum < 0 || portNum > 65535) (throwIO InvalidPortNumber)

    return $ Arguments (fromInteger portNum) cert pw

-- Newer 'base' has Text.Read.readMaybe but alas, that doesn't come with
-- the latest Haskell platform, so let not rely on it
readMaybe :: Read a => String -> Maybe a
readMaybe s = case reads s of
    [(x, "")] -> Just x
    _         -> Nothing

Его поведение отличается при компиляции с помощью оптимизаций:

crabgrass:~/tmp/signserv/src% ghc -fforce-recomp Main.hs && ./Main
Main: WrongArgumentCount
crabgrass:~/tmp/signserv/src% ghc -O -fforce-recomp Main.hs && ./Main
Main: Main.hs:20:9-34: Irrefutable pattern failed for pattern [portStr, cert, pw]

Почему это? Я знаю, что неточные исключения могут быть выбраны произвольно; но здесь мы выбираем одно точное и одно неточное исключение, поэтому оговорка не должна применяться.

4b9b3361

Ответ 1

Я бы согласился с hammar, это похоже на ошибку. И, кажется, это исправлено в HEAD с некоторого времени. С более старым ghc-7.7.20130312, а также с сегодняшним HEAD ghc-7.7.20130521 исключается исключение WrongArgumentCount и удаляется весь другой код main (хулиган для оптимизатора). Тем не менее, все еще сломанный в 7.6.3.

Поведение изменилось в серии 7.2, я получил ожидаемый WrongArgumentCount от 7.0.4, а (оптимизированное) ядро ​​делает это ясным:

Main.main1 =
  \ (s_a11H :: GHC.Prim.State# GHC.Prim.RealWorld) ->
    case GHC.List.$wlen
           @ GHC.Base.String (GHC.Types.[] @ GHC.Base.String) 0
    of _ {
      __DEFAULT ->
        case GHC.Prim.raiseIO#
               @ GHC.Exception.SomeException @ () Main.main7 s_a11H
        of _ { (# new_s_a11K, _ #) ->
        Main.main2 new_s_a11K
        };
      3 -> Main.main2 s_a11H
    }

когда длина пустого списка отличается от 3, поднимите WrongArgumentCount, в противном случае попробуйте сделать все остальное.

С 7.2 и позже оценка длины перемещается за разбор < <27 > :

Main.main1 =
  \ (eta_Xw :: GHC.Prim.State# GHC.Prim.RealWorld) ->
    case Main.main7 of _ {
      [] -> case Data.Maybe.fromJust1 of wild1_00 { };
      : ds_dTy ds1_dTz ->
        case ds_dTy of _ { (x_aOz, ds2_dTA) ->
        case ds2_dTA of _ {
          [] ->
            case ds1_dTz of _ {
              [] ->
                case GHC.List.$wlen
                       @ [GHC.Types.Char] (GHC.Types.[] @ [GHC.Types.Char]) 0
                of _ {
                  __DEFAULT ->
                    case GHC.Prim.raiseIO#
                           @ GHC.Exception.SomeException @ () Main.main6 eta_Xw
                    of wild4_00 {
                    };
                  3 ->

где

Main.main7 =
  Text.ParserCombinators.ReadP.run
    @ GHC.Integer.Type.Integer Main.main8 Main.main3

Main.main8 =
  GHC.Read.$fReadInteger5
    GHC.Read.$fReadInteger_$sconvertInt
    Text.ParserCombinators.ReadPrec.minPrec
    @ GHC.Integer.Type.Integer
    (Text.ParserCombinators.ReadP.$fMonadP_$creturn
       @ GHC.Integer.Type.Integer)

Main.main3 = case lvl_r1YS of wild_00 { }

lvl_r1YS =
  Control.Exception.Base.irrefutPatError
    @ ([GHC.Types.Char], [GHC.Types.Char], [GHC.Types.Char])
    "Except.hs:21:9-34|[portStr, cert, pw]"

Так как throwIO должен уважать порядок действий IO,

Вариант throwIO следует использовать для предпочтения throw для создания исключения в монаде IO, поскольку он гарантирует упорядочение по отношению к другим операциям IO, тогда как throw не делает.

что не должно происходить.

Вы можете принудительно настроить порядок, используя вариант NOINLINE when или выполнив эффектное действие IO перед броском, поэтому кажется, что когда inliner видит, что when ничего не делает, кроме, возможно, бросая, он решает, что порядок не имеет значения.

(Извините, не настоящий ответ, но постарайтесь вставить это в комментарий;)