【问题标题】:How do I create a Show instance for b->a in Haskell?如何在 Haskell 中为 b->a 创建 Show 实例?
【发布时间】:2012-06-28 19:24:10
【问题描述】:

我正在尝试构建一个本质上是二叉树的数据类型,其: 每个节点的左分支是一个函数,可以作用于每个节点的右分支中的变量。我是 Haskell 的新手,我不确定我是否以正确的方式进行操作,但我目前的问题是我无法弄清楚如何将我的类型添加到 Show 类型类中。这是我的尝试:

{-# LANGUAGE ExistentialQuantification #-}
-- file: TS.hs                                                                                                                                                       

data TypeSentence a = forall b. Apply (TypeSentence (b->a)) (TypeSentence b)
                    | Expr a

instance (Show a) => (Show (TypeSentence a)) where
        show (Expr x) = show x
        show (Apply x y) = (show x) ++ " " ++ (show y)

instance (Show (TypeSentence b->a)) where
    show (Expr x) = show "hello"

x = Expr 1
f = Expr (+1)
s = Apply f x

但是,当我将其加载到 ghci 中时,出现以下错误:

TS.hs:9:24:                                                                                                                                                          
     Could not deduce (Show (b -> a)) from the context ()                                                                                                             
     arising from a use of `show' at TS.hs:9:24-29                                                                                                                  
      Possible fix:                                                                                                                                                    
      add (Show (b -> a)) to the context of the constructor `Apply'                                                                                                  
      or add an instance declaration for (Show (b -> a))                                                                                                             
        In the first argument of `(++)', namely `(show x)'                                                                                                               
        In the expression: (show x) ++ " " ++ (show y)                                                                                                                   
        In the definition of `show':                                                                                                                                     
           show (Apply x y) = (show x) ++ " " ++ (show y)                                                                                                               
 Failed, modules loaded: none. 

任何想法如何添加 Show (b->a) 声明?

谢谢。

【问题讨论】:

  • 没有办法为通用函数创建Show 实例,你认为它应该如何工作?我也不认为你真的想要这个。 — 无论如何,首先你的这棵树并没有按照你说的去做:b->a 可以作用于b,但在另一个分支中有一个a。我相信你想把b 放在那里?
  • 是的,你是对的。解决了这个问题。我想在一个通用函数上定义 Show ,这样如果函数有一个 Show 实例,它就会显示它,如果没有,它会在定义中递归,直到它在具有 show 实例的原始函数处触底。
  • 你想写instance Show (TypeSentence (b->a))吗?注意括号
  • import Text.Show.Functions 为您提供 Show 函数实例 (show _ = "<function>")。
  • @Eyal:这是不可能的,因为它违反了参照透明性。有一些f :: (a -> b) -> String 这样f (+ 1) = "(+ 1)" 和f $ \x -> x + 1 = "\\x -> x + 1" 可以让我们区分两个相等的函数;并且函数相等是不可判定的,这使我们无法找到一些“规范形式”来显示。

标签: haskell


【解决方案1】:

您编写的代码存在一些问题,因此我将一一解决。

  1. 您不能为Show (a -> b) 添加特别丰富的实例。考虑一下你必须如何编写它:

    instance Show (a -> b) where
      show f = error "What goes here?"
    

    由于f 是一个函数,除了将它应用到一个值之外,你无能为力;并且由于a 是一个完全多态的类型,因此您不能创建a 类型的值来应用f。所以你唯一的选择是像

    instance Show (a -> b) where
      show _ = "<function>"
    

    正如 Daniel Fischer 在评论中所说,这在 Text.Show.Functions 模块中可用。不过,我实际上不会为此烦恼;我会写一些类似的东西

    instance Show a => Show (TypeSentence a) where
      show (Apply _ x) = "Apply _ " ++ show x -- This still won't work; see below
      show (Expr x)    = "Expr " ++ show x
    

    由于show 只能为任何函数返回一个字符串,因此直接内联即可。

  2. 即便如此,您仍然无法编写Show 实例。如果你尝试编译上面的实例,你会得到以下错误:

    TS.hs:8:36:
        Could not deduce (Show b) arising from a use of `show'
        from the context (Show a)
          bound by the instance declaration
          at TS.hs:7:10-40
        Possible fix:
          add (Show b) to the context of
            the data constructor `Apply'
            or the instance declaration
        In the second argument of `(++)', namely `show x'
        In the expression: "Apply _ " ++ show x
        In an equation for `show': show (Apply _ x) = "Apply _ " ++ show x
    

    问题在于,在您对 TypeSentence 的定义中,Apply 隐藏了一个 TypeSentence 的变量(在 show 的定义中绑定为 x),该变量由一些任意存在隐藏类型 @987654338 参数化@。但是不能保证b 是可显示的,所以show x 不会进行类型检查,这是上面产生的错误:Show b 没有实例,因为b 是任意的。所以要摆脱它,最简单的方法是

    instance Show a => Show (TypeSentence a) where
      show (Apply _ _) = "Apply _ _"
      show (Expr x)    = "Expr " ++ show x
    

    这并不是特别有用。所以对于TypeSentence,也许没有一个好的Show 实例。 (这很好。许多有用的类型没有Show 实例。)

  3. 这个与其他一切无关。 instance Show (TypeSentence b -&gt; a) 声明尝试为从 TypeSentence b 到 a 的函数声明 Show 的实例;如果您将其重新括起来为instance Show (TypeSentence (b -&gt; a)),您仍然需要FlexibleInstances 和OverlappingInstances 扩展来编译它。所以你可能应该只是斧头。

【讨论】:

  • 有没有办法打印类型? GHC 命令的等价物 :type?
  • Anthony:如果你改变数据类型的话。如果Apply 构造函数采用Typeable b 约束,那么您可以在运行时了解b 是什么。
【解决方案2】:

好吧,让我们推理一下。你提议的Show 实例的show 方法将被调用一些函数f :: b -&gt; a。

instance Show (b -> a) where
    show f = ...

您的show 方法可以做什么?好吧,它必须产生一些String,但它会怎么做呢?

好吧,既然f 的类型是b -&gt; a,你唯一能用f 做的就是将它应用到b 类型的东西上。然而show 没有b 类型的参数,并且你的Show 类没有b 类型的任何常量,所以这个show 方法可以用f 做的唯一事情就是应用它到undefined。这可能会或可能不会产生错误,具体取决于 f 是否严格——您无法控制,而且我相信您不希望 show 在某些参数上出错。

但无论如何,即使您确实从 f undefined 得到了结果,该结果的类型也将是 a,而且无论如何您的定义对 a 没有任何作用,因为您没有t 具有可用的a -&gt; whatever 类型的任何功能。 (如果你确实有一个,除非 whatever 是 String,否则你仍然处于相同的位置。)

所以你可以用f做任何事.因此,您的方法的返回值必须是一个常数,或undefined。因为使用undefined 会很傻,所以这个show 方法可以做的唯一明智的事情就是返回一个常量String:

instance Show (b -> a) where
    show _ = "<function>"

正如 Daniel Fischer 在对您的问题的评论中提到的那样,Text.Show.Functions 已经提供了此功能。

但这里的教训是以此为例说明如何推理您的问题。这是有关 Haskell 的巧妙之处之一:您通常可以通过查看类型来证明函数可以做什么、不能做什么或必须做什么。例如,如果你有foo :: (a -&gt; b) -&gt; [a] -&gt; [b],假设foo不够傻不要无偿使用undefined,你可以推断[b]结果中的bs是通过应用a -&gt; b得到的[a] 参数的元素的类型参数。 foo 没有其他方法可以生成 b 类型的值。 (如果你还没猜到,这种类型最自然的函数是map :: (a -&gt; b) -&gt; [a] -&gt; [b]。)

【讨论】:

    【解决方案3】:

    我认为@Davorak 的评论是你想要的。

    https://stackoverflow.com/a/15846061/6289448

    我只是在这里分享。在ghc 8.6.5 中通过测试。

    对于使用 Data.Typeable 的所有函数,有一个不只是固定字符串的部分解决方案。

    {-# LANGUAGE ScopedTypeVariables #-}
    
    import Data.Typeable
    
    instance (Typeable a, Typeable b) => Show (a->b) where
      show _ = show $ typeOf (undefined :: a -> b)
    
    

    在 ghci 中

    > let test :: Int->Int; test x = x + x
    > test
    Int -> Int
    

    不幸的是,如果没有类型签名,类型将默认使用它。

    > let test x = x + x
    > test
    Integer -> Integer
    

    此解决方案适用于多个函数,因为 a -> b -> c 与 a -> (b -> c) 相同,您不妨将其写为 a -> d where d = b -> c。

    > let m10 a b c d e f g h i j = a * b * c * d * e * f * g * h* i * j
    > m10
    Integer -> Integer -> Integer -> Integer -> Integer -> Integer -> Integer
            -> Integer -> Integer -> Integer -> Integer
    

    此方法不起作用,但是当函数的参数是否具有可类型类但未知时,因此 map (+1) 将起作用,但 map 不会。

    > map (+1)
    [Integer] -> [Integer]
    > map
    
    <interactive>:233:1:
    ...
    

    在浏览了 Data.Data 的内部结构和一两个实验之后,似乎可以对其进行重构,使其更加通用,涵盖更多功能。



    如果你不喜欢上面的实现,就自己实现吧! (如果有更好的方法,请告诉我!)

    {-# LANGUAGE FlexibleInstances #-}
    {-# LANGUAGE InstanceSigs #-}
    {-# LANGUAGE ScopedTypeVariables #-}
    
    newtype GenType a =
        GenType
            { asTypeStr :: String
            }
    
    class GenTypeArbitrary a where
        gtArbitrary :: a -> GenType a
    
    instance GenTypeArbitrary String where
        gtArbitrary :: String -> GenType String
        gtArbitrary _ = GenType "String123"
    
    instance GenTypeArbitrary Bool where
        gtArbitrary :: Bool -> GenType Bool
        gtArbitrary _ = GenType "Bool123"
    
    instance GenTypeArbitrary Int where
        gtArbitrary :: Int -> GenType Int
        gtArbitrary _ = GenType "Int123"
    
    instance (GenTypeArbitrary a, GenTypeArbitrary b) => GenTypeArbitrary (a -> b) where
        gtArbitrary :: (GenTypeArbitrary a, GenTypeArbitrary b) => (a -> b) -> GenType (a -> b)
        gtArbitrary _ = GenType $ aTypeStr' ++ " --> " ++ bTypeStr
          where
            aTypeStr = asTypeStr (gtArbitrary (undefined :: a))
            aTypeStr' =
                if "-->" `isInfixOf` aTypeStr
                    then "(" ++ aTypeStr ++ ")"
                    else aTypeStr
            bTypeStr = asTypeStr (gtArbitrary (undefined :: b))
    
    instance  (GenTypeArbitrary a, GenTypeArbitrary b) => Show (a -> b) where
      show f = asTypeStr $ gtArbitrary f
    
    test1 :: Int -> String
    test1 x = ""
    
    test2 :: Int -> String -> Int -> Bool -> Bool
    test2 _ _ _ _ = False
    
    test3 :: Int -> ((String -> Int) -> Bool) -> Bool
    test3 _ _ = False
    
    test4 :: Int -> (Bool -> (String -> Int)) -> Bool
    test4 _ _ = False
    
    
    
    λ > show  (test4)
        "Int123 --> (Bool123 --> String123 --> Int123) --> Bool123"
    it :: String
    
    
    ...
    
    
    λ > show  (test3)
        "Int123 --> ((String123 --> Int123) --> Bool123) --> Bool123"
    it :: String
    
    
    

    【讨论】:

      【解决方案4】:

      如果您的函数的域是有限集,那么您可以在所有点打印函数的值。在 Haskell 中,您可以使用类型类 Ix 和 Bounded 来做到这一点,方法是:

      rangeF :: (Ix a, Bounded a) => [a]
      rangeF = range (minBound, maxBound)
      

      【讨论】:

      • 您也可以使用Enum a 代替Ix a,并使用[minBound..maxBound](等效于enumFromTo minBound maxBound)。
      猜你喜欢
      • 2016-07-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-09-27
      • 1970-01-01
      • 2016-12-05
      • 1970-01-01
      相关资源
      最近更新 更多