【问题标题】:Choosing among alternatives in a Haskell algebraic datatype在 Haskell 代数数据类型中选择备选方案
【发布时间】:2009-12-07 17:17:48
【问题描述】:

当类型X定义为:

data X = 
    X { sVal :: String } |
    I { iVal :: Int } |
    B { bVal :: Bool }

我希望 IntX 值中,如果有,则为零。

returnInt :: X -> Int

如何确定returnInt 的参数是哪种类型的X

【问题讨论】:

  • 要在里面进一步添加一些内容,您的定义基本上是说类型 X 是类型构造函数 X 或类型构造函数 I 或类型构造函数 B。您的函数采用 X 的任何成员,所以你可以只匹配 I,它是 X 的成员。
  • @codebliss:轻微的挑剔:这三个是数据构造函数。 type 构造函数是,例如Maybe,它有一种* -> *:对它应用一个类型,你就会得到一个具体的类型。

标签: data-structures haskell types algebraic-data-types


【解决方案1】:

使用模式匹配。

returnInt :: X -> Int
returnInt (I x) = x
returnInt _     = 0

【讨论】:

    【解决方案2】:

    对所有可能的X 值使用更灵活的定义:

    returnInt :: X -> Maybe Int
    returnInt (I i) = Just i
    returnInt _ = Nothing
    

    然后您可以使用 maybe 来指定您想要的特定默认值 - 0 可能是一个有效值(这被称为 semipredicate problem):

    *Main> maybe 0 id (returnInt $ X "")
    0
    *Main> maybe 0 id (returnInt $ I 123)
    123
    *Main> maybe (-1) id (returnInt $ X "yo")
    -1
    

    相比之下,部分函数有运行时异常的风险:

    *Main> let returnInt (I i) = i
    *Main> :t returnInt
    returnInt :: X -> Int
    *Main> returnInt (B True)
    *** Exception: <interactive>:1:4-22: Non-exhaustive patterns in function returnInt
    

    如果你真的很青蛙,你可以使用MonadPlus

    returnInt :: (MonadPlus m) => X -> m Int
    returnInt (I i) = return i
    returnInt _ = mzero
    

    获得更大的灵活性:

    *Main> maybe 0 id (returnInt $ X "")
    0
    *Main> maybe 0 id (returnInt $ I 123)
    123
    *Main> returnInt (I 123) `mplus` returnInt (I 456) :: [Int]
    [123,456]
    

    【讨论】:

    • 在您的最后一个示例中,您使用MonadPlus 因为它是mzero 方法。最近在设计一个库时,我遇到了这种情况,并决定使用returnfailMonad 类中返回一个值比使用MonadPlus 更好。我知道 fail 被某些人视为一个疣,但我决定使用它是一个更好的概括,因为我们将仅将 MonadPlus 用于它的 mzero 方法。你有什么想法?
    • @jberryman 这取决于上下文。我的一般偏好是通过更优雅的代码为库用户产生更好的抽象。您的问题将成为一个有趣的社区 wiki!
    【解决方案3】:

    给定一个这样的函数:

    returnInt :: X -> Int
    returnInt x = {- some integer -}
    

    ...x 的类型始终为X。您关心的是x 是否使用XIB 类型的构造函数。

    使用模式匹配来区分:

    returnInt :: X -> Int
    returnInt (X _) = error "needed an Int, got a String"
    returnInt (I { iVal = n }) = n
    returnInt (B _) = error "needed an Int, got a Bool"
    

    【讨论】:

      【解决方案4】:

      在这里澄清一点,让我重写你的数据类型以避免在 X 的含义上产生歧义:

      data SomeType = X { myString :: String} | I {myInt :: Int} | B {myBool :: Bool}
      

      在此定义中没有 X、I 和 B 类型。 X、I 和 B 是 创建类型值的构造函数 Sometype 。请注意,当您询问 ghci 使用这些类型构造函数构造的任何值的类型是什么时会发生什么:

      *Main> :t (I 5)
      (I 5) :: Sometype 
      
      *Main> :t (B False)
      (B False) :: Sometype
      

      它们属于同一类型!!

      就像您可以使用 X、I 和 B 来构造类型一样,您可以使用模式匹配来解构类型,就像上面其他答案中所做的那样:

      returnInt :: SomeType -> Int 
      returnInt (I x) = x        -- if the pattern matches (I x) then return x
      returnInt _  = error "I need an integer value, you moron"  -- throw an error otherwise
      

      请记住,模式匹配是按顺序进行的:如果值与某行中的模式匹配,则不会执行下面行中的模式。

      请注意,当您像以前一样使用所谓的记录语法定义类型时(请看这里:http://en.wikibooks.org/wiki/Haskell/More_on_datatypes),您可以免费获得类似的功能!

      尝试查看 myInt 的类型,例如:

      *Main> :t myInt
      myInt :: SomeType -> Int
      

      看看这个函数做了什么:

      *Main> myInt (I 5)
      5
      
      *Main> myInt (B False)
      *** Exception: No match in record selector Main.myInt
      

      这正是上面定义的returnInt 的行为。奇怪的错误信息只是告诉你该函数不知道如何处理与 (I x) 不匹配的 SomeType 类型的成员。

      如果您使用更常见的语法定义类型:

      data SomeType2 = X String | I Int | B Bool
      

      那么你就失去了那些不错的记录功能。

      错误消息终止程序的执行。这有时很烦人。如果您的功能需要更安全的行为,GBacon 的答案就是这样做的方法。了解Maybe a 类型并使用它来处理这种需要返回一些值或什么都不返回的计算(试试这个:http://en.wikibooks.org/wiki/Haskell/Hierarchical_libraries/Maybe)。

      【讨论】:

        猜你喜欢
        • 2015-04-16
        • 2015-01-07
        • 1970-01-01
        • 1970-01-01
        • 2011-10-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多