【问题标题】:How does this trick type-check?这个技巧如何进行类型检查?
【发布时间】:2021-08-03 22:10:22
【问题描述】:

阅读这篇博文 - https://www.haskellforall.com/2021/05/the-trick-to-avoid-deeply-nested-error.html - 我意识到我不明白为什么“技巧”在这种情况下实际上有效:

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

我认为这使得通过绑定传递 () 的“技巧”更加明显 - 值取自外部范围,而 Left 确实使处理短路。

【问题讨论】:

  • 很确定有一个正式的论点,即Either String PersonEither a ()Either String b 的子类型,但我未能在我的(现已删除)答案中提出。
  • 即,Left "Invalid alive string" 可以被视为Either String b 类型的值,无论您为b 选择什么类型,因为值中没有使用b 类型的值。
  • @chepner 不必是Either String PersonEither String anything 就足够了。它在 do 块的 middle 中,没有“提取”值。 :) 所以它可以作为守卫。
  • @WillNess 但是&gt;&gt;=Either a 的定义意味着它将被用作返回值。 pure () 只进行类型检查,因为它不是 do 块中的最后一个表达式。
  • @chepner 啊,是的,但是no:它是不是 l@(Left x) &gt;&gt;= _ = l!右边的Left x 确实创建了一个另一个 类型的new 值,Either String Person 确实如此! (使用相同的x,因此它必须Either String _(非正式写作))。有两种类型不是同一个值,而是两个值。

标签: haskell types typechecking do-notation


【解决方案1】:

因为Either String bEither a () 统一成功,所以会进行类型检查,String ~ ab ~ ()

     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

根据&gt;&gt;= 的定义为Either。至关重要的是,右侧的 Left x 与左侧的 Left x 的值相同。左边的类型为Either String ();右边的确实有 Either String Person 类型,这是 do 块整体的返回类型所要求的。

两个Left x两个不同的值,每个都有自己的特定类型。当然,x :: String 是一样的。

【讨论】:

  • 啊我明白了!我想。因此,由于它位于 do 块的中间,if 块的类型不必与函数返回类型匹配,因为它通过管道传递到另一个函数?
  • @larek,是的,但是两个 if 分支仍然必须匹配。在这里他们做到了,因为一个人可以在左边有任何东西,而另一个人可以在右边有任何东西。
  • 感谢您的出色回答!带我用绑定重写它(见编辑)以正确内化正在发生的事情
  • 我的意思是:do 块对我来说很难
  • @Iarek do notation is our friend。它可以帮助我们。当然,首先我们必须仔细检查特定类型的&gt;&gt;= 代码,以确切了解发生了什么。但它帮助我们分离关注点——一个领域是计算管道中的阴谋,另一个领域是我们程序员正在做的事情。
猜你喜欢
  • 2012-02-18
  • 1970-01-01
  • 1970-01-01
  • 2022-01-06
  • 2015-03-15
  • 1970-01-01
  • 1970-01-01
  • 2016-03-01
  • 2011-03-16
相关资源
最近更新 更多