检查类型是否是Haskell中Show的实例?

Vec*_*tor 7 haskell

假设我在Haskell中有一个简单的数据类型用于存储值:

data V a = V a
Run Code Online (Sandbox Code Playgroud)

我想让V成为Show的一个实例,无论其类型如何.如果a是Show的实例,show (V a)则应返回,show a否则应返回错误消息.或者在Pseudo-Haskell中:

instance Show (V a) where
    show (V a) = if a instanceof Show
                   then show a
                   else "Some Error."
Run Code Online (Sandbox Code Playgroud)

如何在Haskell中实现这种行为?

Lui*_*las 12

正如我在评论中所说,在内存中分配的运行时对象在Haskell程序中没有类型标记.因此,没有instanceof像Java这样的通用操作.

考虑以下因素也很重要.在Haskell中,对于第一个近似(即,忽略了初学者不应该过早解决的一些奇特的东西),所有运行时函数调用都是单态的.即,编译器直接或间接地知道可执行程序中的每个函数调用的单态(非泛型)类型.即使您的V类型show函数具有泛型类型:

-- Specialized to `V a`
show :: V a -> String  -- generic; has variable `a`
Run Code Online (Sandbox Code Playgroud)

...你实际上不能编写一个在运行时调用函数的程序,而不是直接或间接地告诉编译器确切地说a每个调用中的类型.例如:

-- Here you tell it directly that `a := Int`
example1 = show (V (1 :: Int)) 

-- Here you're not saying which type `a` is, but this just "puts off" 
-- the decision—for `example2` to be called, *something* in the call
-- graph will have to pick a monomorphic type for `a`.
example2 :: a -> String
example2 x = show (V x) ++ example1
Run Code Online (Sandbox Code Playgroud)

从这个角度来看,希望你能用你所问的问题发现问题:

instance Show (V a) where
    show (V a) = if a instanceof Show
                   then show a
                   else "Some Error."
Run Code Online (Sandbox Code Playgroud)

基本上,由于a参数的类型将在编译时为任何实际调用show函数而知道,因此在运行时测试此类型没有意义 - 您可以在编译时对其进行测试!一旦你掌握了这一点,你就会被Will Sewell的建议所引导:

-- No call to `show (V x)` will compile unless `x` is of a `Show` type.
instance Show a => Show (V a) where ...
Run Code Online (Sandbox Code Playgroud)

编辑:一个更具建设性的答案可能是这样的:您的V类型需要是多个案例的标记并集.这需要使用GADTs扩展名:

{-# LANGUAGE GADTs #-}

-- This definition requires `GADTs`.  It has two constructors:
data V a where
  -- The `Showable` constructor can only be used with `Show` types.
  Showable   :: Show a => a -> V a
  -- The `Unshowable` constructor can be used with any type.
  Unshowable :: a -> V a

instance Show (V a) where
  show (Showable a) = show a
  show (Unshowable a) = "Some Error."
Run Code Online (Sandbox Code Playgroud)

但这不是运行时检查类型是否是Show实例 - 您的代码负责在编译时知道Showable构造函数的使用位置.


xny*_*hps 9

您可以使用此库:https://github.com/mikeizbicki/ifcxt.能够调用show可能有或没有Show实例的值是它给出的第一个例子之一.这是你如何适应V a:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}

import IfCxt
import Data.Typeable

mkIfCxtInstances ''Show

data V a = V a

instance forall a. IfCxt (Show a) => Show (V a) where
    show (V a) = ifCxt (Proxy::Proxy (Show a))
        (show a)
        "<<unshowable>>"
Run Code Online (Sandbox Code Playgroud)

这是这个库的精髓:

class IfCxt cxt where
    ifCxt :: proxy cxt -> (cxt => a) -> a -> a

instance {-# OVERLAPPABLE #-} IfCxt cxt where ifCxt _ t f = f
Run Code Online (Sandbox Code Playgroud)

我不完全理解它,但这是我认为它的工作原理:

它不会违反"开放世界"的假设

instance {-# OVERLAPPABLE #-} Show a where
    show _ = "<<unshowable>>"
Run Code Online (Sandbox Code Playgroud)

确实.该方法实际上非常类似于:为范围内没有实例的所有类型添加默认情况.但是,它增加了一些间接性,以免弄乱现有实例(并允许不同的函数指定不同的默认值).IfCxt作为一个"元类",一个关于约束的类,用于指示这些实例是否存在,默认情况下指示"false".

instance {-# OVERLAPPABLE #-} IfCxt cxt where ifCxt _ t f = f
Run Code Online (Sandbox Code Playgroud)

它使用TemplateHaskell为该类生成一长串实例:

instance {-# OVERLAPS #-} IfCxt (Show Int) where ifCxt _ t f = t
instance {-# OVERLAPS #-} IfCxt (Show Char) where ifCxt _ t f = t
Run Code Online (Sandbox Code Playgroud)

这也意味着在mkIfCxtInstances被调用时不在范围内的任何实例都将被视为不存在.

proxy cxt参数用于传递Constraint给函数,(cxt => a)参数(我不知道RankNTypes允许)是一个可以使用约束的参数cxt,但只要该参数未使用,就不需要求解约束.这类似于:

f :: (Show (a -> a) => a) -> a -> a
f _ x = x
Run Code Online (Sandbox Code Playgroud)

proxy参数提供了约束,则IfCxt约束求解,无论是tf说法,如果它t再有就是一些IfCxt实例,其中该约束提供,这意味着它可以直接解决,如果它是f那么约束是没有要求,因此被丢弃.


这个解决方案是不完美的(因为新模块可以定义新的Show实例,除非它也调用mkIfCxtInstances,否则它将无法工作),但能够做到这一点违反开放世界的假设.