代码之家  ›  专栏  ›  技术社区  ›  estanford

Haskell中的类型条件控件

  •  2
  • estanford  · 技术社区  · 14 年前

    我要通过 99 Haskell problems 培养我的语言能力。在问题7(“展平嵌套列表结构”)中,我发现自己想要根据传递给函数的参数类型定义条件行为。那是因为

    *Main> :t 1
    1 :: (Num t) => t
    *Main> :t [1,2]
    [1,2] :: (Num t) => [t]
    *Main> :t [[1],[2]]
    [[1],[2]] :: (Num t) => [[t]]
    

    (即嵌套在不同级别的列表具有不同的数据类型)似乎我应该能够编写一个函数来读取参数的类型,然后相应地执行操作。我的第一次尝试是这样的:

    listflatten l = do
        if (:t l) /= ((Num t) => [t]) then
            listflatten (foldl (++) [] l)
            else id l
    

    但当我尝试这样做时,Haskell返回一个解析错误。Haskell是否有足够的灵活性来允许这种类型的操作,我需要找到另一种方法吗?

    4 回复  |  直到 14 年前
        1
  •  12
  •   sastanin    14 年前

    1.改用模式匹配

    您可以解决这个问题,而不必动态检查数据类型。事实上,在Haskell中很少需要它。通常可以使用模式匹配。

    例如,如果您有一个类型

    data List a = Elem a | Nested [List a]
    

    flatten (Elem x) = ...
    flatten (Nested xs) = ...
    
    例子:
    data List a = Elem a | Nested [List a]
      deriving (Show)
    
    nested = Nested [Elem 1, Nested [Elem 2, Elem 3, Nested [Elem 4]], Elem 5]
    main = print $ flatten nested
    
    flatten :: List a -> [a]
    flatten (Elem x) = [x]
    flatten (Nested lists) = concat . map flatten $ lists  
    

    map flatten 将每个内部列表展平,因此它的行为类似于 [List a] -> [[a]] ,我们在这里生成一个列表列表。 concat 将所有列表合并在一起( concat [[1],[2,3],[4]] 给予 [1,2,3,4] concat . map flatten 与相同 concatMap flatten .

    2.要动态检查类型,请使用Data.Typeable

    稀有的 如果您真的需要动态地检查类型,您可以使用 Data.Typeable 类型类及其应用 typeOf :t 只适用于GHCI,它不是语言的一部分。

    ghci> :m + Data.Typeable
    ghci> typeOf 3 == typeOf "3"
    False
    ghci> typeOf "a" == typeOf "b"
    True
    

    很可能,您需要使用 DeriveDataTypeable

        2
  •  6
  •   Antal Spector-Zabusky    14 年前

    (抱歉,我走得有点远/太深了。CliffsNotes的版本是“不,你不能真正做你想做的事情,因为类型不是值,我们不能给你的函数一个合理的类型;使用您自己的数据类型。第一段和第五段(不包括这一段或代码块)解释了我所说的第一部分的核心意思,答案的其余部分应该提供一些澄清/细节。)

    粗略地说,不,这是不可能的,有两个原因。第一个是类型分派问题。这个 :t 命令是GHCi的一个特性(非常有用的特性),而不是Haskell函数。想想原因:它会是什么类型的? :t :: a -> ? ? 类型本身不是值,因此没有类型。这是两个不同的世界。所以你想这么做是不可能的。还要注意,你有一个随机的 do . 这很糟糕

    为什么会这样?Haskell有两种多态性,我们现在关注的是 parametric polymorphism . 这就是当你有这样的类型时你所看到的 concat :: [[a]] -> a . 那个 a 说的是 concat 必须适用于所有可能的情况 从现在到时间的尽头。你到底要怎么打字 flatten 使用此方案?这是不可能的。

    您正在尝试为不同类型的数据调用不同的函数,即席定义。令人震惊的是, ad-hoc polymorphism . 例如,在C++中,可以定义以下函数:

    template <typename T>
    void flatten(vector<T>& v) { ... }
    
    template <typename T>
    void flatten(vector< vector<T> >& v) { ... }
    

    这将允许你为不同的类型做不同的事情。你甚至可以 template <> void flatten(int) { ... } ! 你呢 可以 在Haskell中,通过使用类型类(如 Num Show Show a => a -> String 不同的函数可以为不同的 s。事实上,你可以利用这个来得到问题的部分解决方案,但是在我们这样做之前,让我们看看第二个问题。

    这个问题与您试图输入的列表有关。Haskell的列表类型定义为(大致) data [a] = [] | a : [a] . 换句话说,列表中的每个元素都必须具有相同的类型;INT列表, [Int] ,仅包含整数, Int [[Int]] ,只包含int的列表, [内景] . 结构 [1,2,[3,4],5] 是违法的!读你的代码,我想你明白这一点;然而,还有另一个分支。出于类似的原因,您无法编写类型为的完全泛型展平函数 flatten :: [...[a]...] -> [a] 功能 深度 ,这在列表中仍然是不可能的。你需要 [a] , [[a]]

    因此,要获得所有必要的属性,您需要一个不同的类型。您想要的类型有一个不同的属性:它要么不包含任何内容,要么是一个元素后跟值的其余部分,要么是元素的嵌套列表后跟值的其余部分。换句话说,就像

    data NList a = Nil
                 | a         :>  NList a
                 | (NList a) :>> NList a
                 deriving (Eq, Show)
    infixr 5 :>, :>>
    

    然后,代替列表 [1,2,3] == 1 : 2 : 3 : [] ,你会写 1 :> 2 :> 3 :> Nil (1 (2 3) 4 ()) ,你会写 1 :> (2 :> 3 :> Nil) :>> 4 :> Nil :>> Nil . 您甚至可以开始定义函数来操作它:

    nhead :: NList a -> Either a [a]
    nhead Nil       = error "nhead: Empty NList."
    nhead (h :>  _) = Left  a
    nhead (h :>> _) = Right a
    
    ntail :: NList a -> NList a
    ntail Nil       = error "nhead: Empty NList."
    ntail (_ :>  t) = t
    ntail (_ :>> t) = t
    

    data NestedList a = Elem a
                      | List [NestedList a]
                      deriving (Eq, Show)
    

    List [Elem 1, Elem 2, Elem 3] List [Elem 1, List [Elem 2, Elem 3], Elem 4, List []]


    别用这个! 说真的。另一种技术几乎肯定是更好的选择。但是,这种技术很酷,所以我将在这里介绍它。考虑以下代码:

    {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, UndecidableInstances #-}
    
    class Flattenable f e where
      flatten :: f -> [e]
    
    instance Flattenable a a where
      flatten = return
    
    instance Flattenable f e => Flattenable [f] e where
      flatten = concatMap flatten
    

    我们正在创建一个 类型类 我们可以把他们的例子平复。如果我们有 Flattenable f e ,那么 f 应该是一个集合,在本例中是一个列表,其元素最终为 e . 任何单个对象都是这样一个集合,其元素类型是它自己;因此,第一个实例声明允许我们将任何内容展平到一个单例列表中。二审声明说如果我们能把 进入一个列表 e s、 然后我们还可以将 f e 通过将每个 f 把结果列在一起。这个递归类定义为嵌套的列表类型递归地定义函数,使您能够用单个函数展平任何嵌套的列表 压扁 : [1,2,3] , [[4,5],[6]] [[[7,8],[9]],[[10]],[[11],[12]]] ,等等。

    但是,由于有多个实例等原因,它确实需要一个单一类型的注释:例如,您需要编写, flatten [[True,False],[True]] :: [Bool] . 如果你的列表中有类型类多态性的东西,那么事情就要严格一点;你需要写信 flatten [[1],[2,3 :: Int]] :: [Int] ,据我所知,结果列表本身不能是多态的(然而,我很可能对这最后一部分是错误的,因为我没有尝试任何方法的一切。)出于类似的原因,这是太开放,你可以声明 instance Flattenable [f] () where flatten = [()] 如果你也想的话。我试着用类型族/函数依赖来解决一些问题,但是由于递归结构的存在,我无法让它工作(我没有 e 以及一份关于 type Elem a = a type Elem [f] = Elem f 但这些矛盾 [f] 比赛 ). 如果有人知道怎么做,我很想看看!

    再说一次,我很抱歉,我累了就开始唠叨。不过,我还是希望这会有所帮助!

        3
  •  5
  •   Volker Stolz    14 年前

    您混淆了交互式命令 :t 在具有内置函数的解释器中。无法在运行时查询类型。

        4
  •  3
  •   sepp2k    14 年前

    flatten (List [Elem 1, List [Elem 2, List [Elem 3, Elem 4], Elem 5]])
    

    如您所见,问题需要您为任意嵌套的列表创建自己的数据结构。

    普通haskell列表不能任意嵌套。列表中的每个元素都必须具有相同的类型(静态已知),这就是为什么动态检查元素的类型没有意义的原因。

    一般来说,haskell不允许您创建不同类型的列表,然后在运行时检查该类型。您可以使用typeclasses为具有不同类型参数的flatten定义不同的行为,但这仍然不能提供任意嵌套的列表。