如何让Haskell中的函数依赖于其参数的类型?

时间:2014-10-12 17:45:47

标签: haskell types pattern-matching pattern-guards

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

这是我尝试过的,没有编译的内容:

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

3 个答案:

答案 0 :(得分:5)

如果可能,您应该将String类型的值包装在newtype中,如@wowofbob所示。

然而,有时这是不可行的,在这种情况下,有两种通用的方法可以使某些内容专门识别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-ish方法,因为它意味着调用者无法详细说明该函数将从该类型中做什么。

类型类通常给出约束多态性,因为特定函数行为的唯一变化必须来自相关类型类实例中的变体。 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。但这有点模糊,所以我认为最好是明确的。

答案 1 :(得分:4)

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

newtype PrettyString = PrettyString { toString :: String }

instance Show PrettyString where
  show (PrettyString s) = "$$" ++ s ++ "$$" -- for example

然后像下面这样使用它:

main = getLine >>= print . PrettyString

答案 2 :(得分:2)

<强> TL; DR:

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

<强>买者
为了记录,使用newtype包装器的wowofbob's answer是我在现实生活中使用的简单,干净的解决方案,但我觉得看看Prelude如何做到这一点是有益的。

默认隐藏逗号

在前奏中完成此操作的方法是使Show类具有显示列表的功能,并使用您可以覆盖的默认定义。

import Data.List (intercalate)

我会使用intercalate :: [a] -> [[a]] -> [a]在逗号之间插入逗号:

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

创建一个showList_类函数,默认为show和逗号分隔列表。

现在,该类具有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>