假设我在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构造函数的使用位置.
您可以使用此库: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约束求解,无论是t或f说法,如果它t再有就是一些IfCxt实例,其中该约束提供,这意味着它可以直接解决,如果它是f那么约束是没有要求,因此被丢弃.
这个解决方案是不完美的(因为新模块可以定义新的Show实例,除非它也调用mkIfCxtInstances,否则它将无法工作),但能够做到这一点会违反开放世界的假设.