这个技巧如何进行类型检查?
How does this trick type-check?
阅读此博客 post – https://www.haskellforall.com/2021/05/the-trick-to-avoid-deeply-nested-error.html – 我意识到我不明白为什么 'trick' 在这种情况下实际上有效:
{-# LANGUAGE NamedFieldPuns #-}
import Text.Read (readMaybe)
data Person = Person { age :: Int, alive :: Bool } deriving (Show)
example :: String -> String -> Either String Person
example ageString aliveString = do
age <- case readMaybe ageString of
Nothing -> Left "Invalid age string"
Just age -> pure age
if age < 0
then Left "Negative age"
else pure ()
alive <- case readMaybe aliveString of
Nothing -> Left "Invalid alive string"
Just alive -> pure alive
pure Person{ age, alive }
具体来说,我很难理解为什么会这样
if age < 0
then Left "Negative age"
else pure ()
类型检查。
Left "Negative age"
的类型为 Either String b
而
pure ()
的类型是 Either a ()
为什么会这样?
编辑:我将代码简化并重新编写为绑定操作而不是 do
块,然后看到 Will 对他已经很出色的答案的编辑:
{-# LANGUAGE NamedFieldPuns #-}
import Text.Read (readMaybe)
newtype Person = Person { age :: Int} deriving (Show)
example :: String -> Either String Person
example ageString =
getAge ageString
>>= (\age -> checkAge age
>>= (\()-> createPerson age))
getAge :: Read b => String -> Either [Char] b
getAge ageString = case readMaybe ageString of
Nothing -> Left "Invalid age string"
Just age -> pure age
checkAge :: (Ord a, Num a) => a -> Either [Char] ()
checkAge a = if a < 0
then Left "Negative age"
else pure ()
createPerson :: Applicative f => Int -> f Person
createPerson a = pure Person { age = a }
我认为这使得通过绑定传递 ()
的 'trick' 更加明显 - 值取自外部范围,而 Left
确实使处理短路.
它进行类型检查,因为 Either String b
和 Either a ()
成功统一,String ~ a
和 b ~ ()
:
Either String b
Either a ()
------------------
Either String () a ~ String, b ~ ()
它出现在类型Either String Person
的do
块中,所以没关系,因为它是同一个monad,Either
,具有相同的“错误信号”类型,String
.
出现在do
块的中间,没有值“提取”。所以它起到了看守的作用。
它是这样的:如果它是 Right y
,那么 do
块的翻译是
Right y >>= (\ _ -> .....)
并在 .....
内继续计算,忽略 y
值。但是如果是Left x
,那么
Left x >>= _ = Left x
根据 >>=
对 Either
的定义。至关重要的是,右边的 Left x
与左边的 Left x
是 而不是 相同的值。左侧的类型为 Either String ()
;右边的确实是 Either String Person
类型,正如整个 do
块的 return 类型所要求的那样。
两个Left x
是两个不同的值,每个都有自己特定的类型。 x :: String
当然是一样的。
阅读此博客 post – https://www.haskellforall.com/2021/05/the-trick-to-avoid-deeply-nested-error.html – 我意识到我不明白为什么 'trick' 在这种情况下实际上有效:
{-# LANGUAGE NamedFieldPuns #-}
import Text.Read (readMaybe)
data Person = Person { age :: Int, alive :: Bool } deriving (Show)
example :: String -> String -> Either String Person
example ageString aliveString = do
age <- case readMaybe ageString of
Nothing -> Left "Invalid age string"
Just age -> pure age
if age < 0
then Left "Negative age"
else pure ()
alive <- case readMaybe aliveString of
Nothing -> Left "Invalid alive string"
Just alive -> pure alive
pure Person{ age, alive }
具体来说,我很难理解为什么会这样
if age < 0
then Left "Negative age"
else pure ()
类型检查。
Left "Negative age"
的类型为 Either String b
而
pure ()
的类型是 Either a ()
为什么会这样?
编辑:我将代码简化并重新编写为绑定操作而不是 do
块,然后看到 Will 对他已经很出色的答案的编辑:
{-# LANGUAGE NamedFieldPuns #-}
import Text.Read (readMaybe)
newtype Person = Person { age :: Int} deriving (Show)
example :: String -> Either String Person
example ageString =
getAge ageString
>>= (\age -> checkAge age
>>= (\()-> createPerson age))
getAge :: Read b => String -> Either [Char] b
getAge ageString = case readMaybe ageString of
Nothing -> Left "Invalid age string"
Just age -> pure age
checkAge :: (Ord a, Num a) => a -> Either [Char] ()
checkAge a = if a < 0
then Left "Negative age"
else pure ()
createPerson :: Applicative f => Int -> f Person
createPerson a = pure Person { age = a }
我认为这使得通过绑定传递 ()
的 'trick' 更加明显 - 值取自外部范围,而 Left
确实使处理短路.
它进行类型检查,因为 Either String b
和 Either a ()
成功统一,String ~ a
和 b ~ ()
:
Either String b
Either a ()
------------------
Either String () a ~ String, b ~ ()
它出现在类型Either String Person
的do
块中,所以没关系,因为它是同一个monad,Either
,具有相同的“错误信号”类型,String
.
出现在do
块的中间,没有值“提取”。所以它起到了看守的作用。
它是这样的:如果它是 Right y
,那么 do
块的翻译是
Right y >>= (\ _ -> .....)
并在 .....
内继续计算,忽略 y
值。但是如果是Left x
,那么
Left x >>= _ = Left x
根据 >>=
对 Either
的定义。至关重要的是,右边的 Left x
与左边的 Left x
是 而不是 相同的值。左侧的类型为 Either String ()
;右边的确实是 Either String Person
类型,正如整个 do
块的 return 类型所要求的那样。
两个Left x
是两个不同的值,每个都有自己特定的类型。 x :: String
当然是一样的。