【问题标题】:How to tell whether variable is a certain data in Haskell?如何判断变量是否是 Haskell 中的某个数据?
【发布时间】:2014-12-27 06:42:48
【问题描述】:

编辑:QWhere 的此类实例在传递如下输入时失败:>qWhere fly john 即使flyArgument -> Argument -> Predicate 类型并且johnArgument 类型。

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}

data Argument = Argument { ttype :: Type, value :: String } deriving (Show, Eq)
data Predicate = Predicate { lemma :: String, arguments :: [Argument] } deriving (Show, Eq)

class Fly a b where
      fly :: a -> b -> Predicate

instance Fly Argument Argument where
      fly x y = Predicate { lemma = "fly", arguments = [x, y] }

instance Fly Argument Predicate where
      fly x y = Predicate { lemma = "fly", arguments = [x, arguments y !! 0] }

class QWhere a b where
            qWhere :: a -> b -> String

instance QWhere (Argument -> Argument -> Predicate) Argument where
            qWhere x y = "hi"

这是 ghci 的输出:

No instance for (QWhere (a0 -> b0 -> Predicate) Argument)
  arising from a use of ‘qWhere’
The type variables ‘a0’, ‘b0’ are ambiguous
Note: there is a potential instance available:
  instance QWhere (Argument -> Argument -> Predicate) Argument
    -- Defined at new_context.hs:116:10
In the expression: qWhere fly john
In an equation for ‘it’: it = qWhere fly john

No instance for (Fly a0 b0) arising from a use of ‘fly’
The type variables ‘a0’, ‘b0’ are ambiguous
Note: there are several potential instances:
  instance Fly Argument Predicate
    -- Defined at new_context.hs:110:10
  instance Fly Argument Argument
    -- Defined at new_context.hs:107:10
In the first argument of ‘qWhere’, namely ‘fly’
In the expression: qWhere fly john
In an equation for ‘it’: it = qWhere fly john

这些问题是相关的,但没有一个答案能解决我的问题。

(1)Checking for a particular data constructor

(2)Test if Haskell variable matches user-defined data type option

还有一些互联网资源可以解决这个问题,但我找不到解决方案:

(3)https://www.haskell.org/haskellwiki/Determining_the_type_of_an_expression

(4)http://okmij.org/ftp/Haskell/typeEQ.html

我的问题:我定义了两种 Haskell data 类型。我得到了一个输入,我需要确定它是属于数据类型 A 还是数据类型 B。

这里是数据类型定义:

data Argument = Argument { ttype :: Type, value :: String } deriving (Show, Eq)
data Predicate = Predicate { lemma :: String, arguments :: [Argument] } deriving (Show, Eq)

如果变量是数据类型参数或谓词,我需要一个返回真/假的函数。

我试图遵循这两个 SO 问题的答案,但只收到 ghci 编译器的投诉:

--checks if a variable is of data type Argument
--this does not compile (from question (2))
isArgument :: a -> Bool
isArgument (Argument _) = True
isArgument _ = False

--from question (1), also fails
isArgument :: a -> String
isArgument value = 
    case token of
        Argument arg -> "is argument"
        Predicate p -> "is predicate"

【问题讨论】:

    标签: haskell polymorphism


    【解决方案1】:

    您可以通过使 Argument 和 Predicate 成为同一类型的一部分来做您想做的事情......

    data LogicElement = Argument { ttype :: Type, value :: String } |
                Predicate { lemma :: String, arguments :: [LogicElement] } deriving (Show, Eq)
    

    虽然可以定义一个类型为 (a->Bool) 的函数,但这是不寻常的,通常意味着输入的值将被忽略(即,如果你不这样做,你怎么能对某事做任何事情呢?甚至知道它是什么?您几乎只能在其上应用其他 (a->b) 函数。

    在特定的例子中,你的编译器会在下面抱怨

    isArgument (Argument _) = True
    

    因为该模式隐含地暗示输入参数必须是 Argument 类型,而您给出的签名是未定义类型 a

    【讨论】:

    • 第一个解决方案由于某种原因不起作用,因为其中一个字段是参数。如果我删除“arguments :: [Argument]”它会编译,但我需要那个字段。
    • 糟糕,我将argument 的类型保留为Argument,而它也应该更改为新的类型名称LogicElement。我已经进行了更改,现在应该可以编译了,尽管请注意您可能不喜欢谓词可以嵌套为参数...。为了更好地解决这个问题,我需要知道为什么需要 isArgument 函数开头.
    【解决方案2】:

    您尝试做的那种动态类型在 Haskell 中很少使用。如果您想编写可以同时接受 PredicateArgument 值的函数,则至少有两种惯用方式,具体取决于您的具体用例。

    首先是使用类型类重载函数。例如

    class IsArgument a where
        isArgument :: a -> Bool
    
    instance IsArgument Argument where
        isArgument _ = True
    
    instance IsArgument Predicate where
        isArgument _ = False
    

    第二种是使用一些 sum-type,例如 Either Predicate Argument 或自定义 sum-type,例如:

    data Expr = ArgumentExpr Argument | PredicateExpr Predicate
    
    isArgument :: Expr -> Bool
    isArgument (ArgumentExpr _) = True
    isArgument _                = False
    

    您也可以将ArgumentPredicate 构造函数设置为相同类型,但当然会失去将它们视为单独类型的类型安全性。您可以通过使用 GADT 并使用幻像类型标记构造函数来规避这种情况,但这会涉及 GHC 提供的稍微更高级的类型扩展:

    {-# LANGUAGE GADTs #-}
    {-# LANGUAGE DataKinds #-}
    {-# LANGUAGE StandaloneDeriving #-}
    
    data ExprType = Argument | Predicate
    
    data Expr t where
        ArgumentExpr  :: { ttype :: Type, value :: String } -> Expr Argument
        PredicateExpr :: { lemma :: String, arguments :: [Expr Argument] } -> Expr Predicate
    
    deriving instance Show (Expr t)
    deriving instance Eq (Expr t)
    
    isArgument :: Expr t -> Bool
    isArgument (ArgumentExpr {}) = True
    isArgument _ = False
    

    现在参数和谓词是相同类型的构造函数,但是您可以通过使用类型参数将值限制为特定构造函数,就像在 arguments :: [Expr Argument] 中所做的那样,但您也可以只接受使用类型 Expr t 的任何表达式在isArgument

    如果您真的需要运行时多态,可以使用Typeable 类型类来实现,它使您能够获取运行时类型信息并对不透明的泛型类型进行类型转换。

    {-# LANGUAGE DeriveDataTypeable #-}
    
    import Data.Typeable
    
    data Argument = Argument { ttype :: Type, value :: String } deriving (Show, Eq, Typeable)
    data Predicate = Predicate { lemma :: String, arguments :: [Argument] } deriving (Show, Eq, Typeable)
    
    isArgument :: Typeable a => a -> Bool
    isArgument a = case cast a of
        Just (Argument {}) -> True
        _                  -> False
    

    函数cast 尝试将任何Typeable a => a 值转换为某种已知类型,如果类型转换成功则返回Just 值,如果类型转换失败则返回Nothing

    【讨论】:

    • 我希望使用一个类来做到这一点,但实现说明特别要求一个函数而不是一个类。
    • 好的,那么您可能应该使用 sum-type 方法。
    【解决方案3】:

    当您说isArgument 具有a -> Bool 类型时,您是说它可以采用任何,或者更确切地说所有可能a,而不仅仅是某些。不过,有一些解决方案。我的偏好是,对于简单的替代方案,只需使用Either

    import Data.Either
    
    isArgument :: Either Argument Predicate -> Bool
    isArgument = isLeft
    

    或者

    -- Note the use of {} instead of _, the {} expands to all fields in a record
    -- the _ only would have taken the place of the ttype field, although you could have
    -- (Argument _ _) to capture both fields of the Argument constructor
    isArgument :: Either Argument Predicate -> Bool
    isArgument (Left (Argument {})) = True
    isArgument _ = False
    

    虽然,这种函数的唯一用途是当你不确定你拥有哪种数据类型,并且 Haskell 中的值不能被模棱两可地键入时。如果您在 Java/C/C++/C# 中使用类似

    if (some_condition) {
        x = "a string";
    } else {
        x = 5;
    }
    

    这些是静态类型语言,一个变量不能接受两种不同类型的值。 Haskell 也是如此。如果你想为一个可以接受两个不同值的变量赋予一个类型,你必须为它编写一个容器。 Haskell 中的Either 容器是为此目的预定义的。

    【讨论】:

    • 我尝试了提供的两种解决方案,但在传递实例时它们都失败了,例如“isArgument john”会导致解释器抱怨,即使“john”是数据类型“Argument”的一个实例
    • @user3898238 您必须将Argument 类型的每个值包装在Left 构造函数中,并将Predicate 类型的每个值包装在Right 构造函数中。这种转换不会自动发生。例如:filter isArgument [Left (Argument x "an arg"), Right (Predicate "lemma" []), Left (Argument y "another arg")] 将返回 [Left (Argument x "an arg"), Left (Argument y "another arg")]。这听起来像是XY Problem 的一个实例。如果您发布您正在尝试做的事情,而不是您想做的事情,也许会有所帮助。
    猜你喜欢
    • 1970-01-01
    • 2011-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-05
    • 1970-01-01
    • 2011-12-11
    • 1970-01-01
    相关资源
    最近更新 更多