【问题标题】:How do I let a function in Haskell depend on the type of its argument?如何让 Haskell 中的函数取决于其参数的类型?
【发布时间】:2014-10-12 17:45:47
【问题描述】:

我试图在show 上编写一个变体,它通过不包括 " 并直接返回字符串来将字符串与Show 的其他实例不同地处理。但我不知道该怎么做。模式匹配?警卫?我在任何文档中都找不到任何相关内容。

这是我尝试过的,但无法编译:

show_ :: Show a => a -> String
show_ (x :: String) = x
show_ x             = show x

【问题讨论】:

    标签: haskell types pattern-matching pattern-guards


    【解决方案1】:

    如果可能,您应该按照@wowofbob 的建议将String 类型的值包装在newtype 中。

    但是,有时这是不可行的,在这种情况下,有两种通用方法可以让某物专门识别 String

    第一种方法,是自然的 Haskell 方法,是使用一个类型类,就像 Show 一样,为不同的类型获得不同的行为。所以你可以写

    class Show_ a where
        show_ :: a -> String
    

    然后

    instance Show_ String where
        show_ x = x
    
    instance Show_ Int where
        show_ x = show x
    

    对于您要使用的任何其他类型,依此类推。这样做的缺点是您需要为所有想要的类型显式写出 Show_ 实例。

    @AndrewC 展示了如何将每个实例缩减为一行,但您仍然必须明确列出它们。理论上你可以解决这个问题,详见this question,但这并不令人愉快。

    第二个选项是使用Typeable 类获取真正的运行时类型信息,在这种特殊情况下它非常简短:

    import Data.Typeable
    
    [...]
    
    show_ :: (Typeable a, Show a) => a -> String
    show_ x =
        case cast x :: Maybe String of
            Just s -> s
            Nothing -> show x
    

    这不是一种自然的 Haskell 式方法,因为这意味着调用者无法从类型中得知函数将做什么。

    类型类通常赋予约束多态性,因为特定函数行为的唯一变化必须来自相关类型类实例的变化。 Show_ 类从它的名称中可以看出它的含义,并且可能会被记录在案。

    但是Typeable 是一个非常普通的类。您将所有内容委托给您正在调用的特定功能;具有Typeable 约束的函数可能对许多不同的具体类型有完全不同的实现。

    最后,对更接近原始代码的Typeable 解决方案的进一步阐述是使用几个扩展:

    {-# LANGUAGE ViewPatterns, ScopedTypeVariables #-}
    import Data.Typeable
    
    [...]
    
    show_ :: (Typeable a, Show a) => a -> String
    show_ (cast -> Just (s :: String)) = s
    show_ x = show x
    

    ViewPatterns 的使用允许我们在模式中编写cast,这可能更适合更复杂的示例。事实上,我们可以省略:: String 类型约束,因为这种情况的主体强制s 成为show_ 的结果类型,即String,无论如何。但这有点晦涩,所以我认为最好是明确的。

    【讨论】:

      【解决方案2】:

      您可以将其包装到 newtype 并为其创建自定义 Show 实例:

      newtype PrettyString = PrettyString { toString :: String }
      
      instance Show PrettyString where
        show (PrettyString s) = "$$" ++ s ++ "$$" -- for example
      

      然后像下面这样使用它:

      main = getLine >>= print . PrettyString
      

      【讨论】:

        【解决方案3】:

        TL;DR:

        复制前奏的方式并使用showList_作为类函数来生成列表的实例,以便您可以覆盖String的定义。

        警告
        郑重声明,wowofbob's answer 使用 newtype 包装器是我在现实生活中会使用的简单、干净的解决方案,但我觉得看看 Prelude 如何做到这一点也很有启发性。

        默认插入逗号

        在前奏中这样做的方式是使Show 类具有显示列表的功能,并具有可以覆盖的默认定义。

        import Data.List (intercalate)
        

        我将使用intercalate :: [a] -> [[a]] -> [a] 在内容之间添加逗号:

        ghci> intercalate "_._" ["intercalate","works","like","this"]
        "intercalate_._works_._like_._this"
        

        创建一个 showList_ 类函数,默认显示和逗号分隔的列表。

        所以现在这个类具有showList 函数的默认实现,重要的是,一个默认的show_ 实现只使用普通的show 函数。为了能够使用它,我们必须坚持该类型已经在 Show 类型类中,但据我了解,这没关系。

        class Show a => Show_ a where
          show_ :: a -> String
          showList_ :: [a] -> String
        
          show_ = show
          showList_ xs = '[' : intercalate ", " (map show_ xs) ++ "]"
        

        真正的 Show 类出于效率原因直接使用 String -> String 类型的函数而不是 String,并使用优先级参数来控制括号的使用,但为简单起见,我将跳过所有这些。

        自动为列表创建实例

        现在我们可以使用showList 函数为列表提供一个实例:

        instance Show_ a => Show_ [a] where
           show_ xs = showList_ xs
        

        Show a => 超类使实例变得超级简单

        现在我们来看一些例子。由于我们默认的show_ 实现,我们不需要做任何实际的编程,除非我们想要覆盖默认值,我们将为Char 做这件事,因为String ~ [Char]

        instance Show_ Int
        instance Show_ Integer
        instance Show_ Double
        
        instance Show_ Char where
           show_ c = [c] -- so show_ 'd' = "d". You can put show_ = show if you want "'d'"
           showList_ = id -- just return the string
        

        在实践中:

        现在在 ghci 的输出中隐藏 " 并没有多大用处,因为默认的 show 函数用于此目的,但如果我们使用 putStrLn,引号就会消失:

        put :: Show_ a => a -> IO ()
        put = putStrLn . show_
        
        ghci> show "hello"
        "\"hello\""
        ghci> show_ "hello"
        "hello"
        ghci> put "hello"
        hello
        ghci> put [2,3,4]
        [2, 3, 4]
        ghci> 
        

        【讨论】:

          猜你喜欢
          • 2021-10-15
          • 1970-01-01
          • 2021-10-20
          • 1970-01-01
          • 2022-01-11
          • 1970-01-01
          • 2023-02-09
          • 1970-01-01
          • 2021-05-13
          相关资源
          最近更新 更多