我正在尝试定义一个函数来检测输入的类型是否满足给定的约束:
satisfies :: (c a => a -> b) -> a -> Maybe b
-- or the more general
claim :: (c => a) -> Maybe a
所以期望的行为是:
>>> :t satisfies @Show show
satisfies @Show show :: a -> Maybe String
>>> satisfies @Show show (0 :: Int)
Just "0"
>>> satisfies @Show show (id :: Int -> Int)
Nothing
目标是使定义完全多态函数变得容易 尽可能优化专业化:
showAny :: a -> String
showAny (satisfies @Show show -> Just str) = str
showAny (satisfies @Typeable showType -> Just str) = "_ :: " ++ str
showAny _ = "_"
作为我可以尝试的最简单的事情,我的第一次尝试尝试使用-fdefer-to-runtime
{-# OPTIONS_GHC -fdefer-type-errors -Wno-deferred-type-errors #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE RankNTypes #-}
module Claim where
import System.IO.Unsafe (unsafePerformIO)
import System.IO.Error (catchIOError)
satisfies :: (c a => a -> b) -> a -> Maybe b
satisfies f a = unsafePerformIO $
(return . Just $! f a) `catchIOError` \_ -> return Nothing
此操作失败,因为-fdefer-type-errors
不会将检查推迟到
运行时,或允许在上下文中进一步检查
实际使用(正如我所希望的那样),但在编译时替换了找到的
类型错误,相当于error "MESSAGE"
。
现在我没有想法。是否可以实施satisfies
?
答案 0 :(得分:6)
您无法在运行时调度实例可用性。请记住,编译器将约束转换为类型类字典 - 显式传递并在运行时显式访问的函数记录。 "胖箭" =>
在运行时由一个"细箭头"表示。 ->
,因此elaborator需要在编译时知道要传递哪个字典。
即,以下原始示例:
class Show a where
show :: a -> String
instance Show String where
show = id
showTwice :: Show a => a -> String
showTwice x = show x ++ show x
main = putStrLn $ showTwice "foo"
生成类似于:
的核心代码data Show_ a = Show_ { show :: a -> String }
showString_ :: Show_ String
showString_ = Show_ { show = id }
showTwice :: Show_ a -> a -> String
showTwice show_ x = show show_ x ++ show show_ x
main = putStrLn $ showTwice showString_ "foo"
为main
生成代码时,编译器需要知道在哪里找showString_
。
您可以想象一个系统,您可以在运行时使用某种内省机制查找类型类字典,但从语言设计的角度来看,这会产生奇怪的行为。问题是孤儿实例。如果我编写一个试图在模块A
中查找给定实例的函数,并在不相关的模块B
中定义这样的实例,那么从某个客户端模块调用该函数时的行为{{ 1}}取决于C
是否由程序的其他部分导入。太奇怪了!
更常用的做法"完全多态函数,在可能的情况下利用特化"将问题中的函数放入类型类本身并给它一个默认实现(如果默认实现依赖于某个超类,可能使用a default
signature)。您的B
将如下所示:
showAny
您需要为要使用{-# LANGUAGE DefaultSignatures #-}
import Data.Typeable
class ShowAny a where
showAny :: a -> String
default showAny :: Typeable a => a -> String
showAny x = "_ :: " ++ show (typeOf x)
的所有类型实施ShowAny
,但这通常只需一行代码,
showAny
并且您可以通过覆盖instance (Typeable a, Typeable b) => ShowAny (a -> b)
来专门化给定类型的实现。
showAny
在执行通用编程的库中,您经常会看到这种方法。例如,aeson
可以使用instance ShowAny String where
showAny = id
将给定类型与JSON串行化(您所要做的就是派生GHC.Generics
并写两行Generic
),但是如果通用代码不够快或您需要自定义输出,您也可以编写自己的instance ToJSON MyType; instance FromJSON MyType
和ToJSON
实例。
答案 1 :(得分:0)
the accepted answer的替代解决方法是手动传递字典。
即,给定:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeOperators #-}
module Claim where
data Proof c where QED :: c => Proof c
type Claim c = Maybe (Proof c)
type c ? a = Maybe (Proof (c a))
可以写:
showAny :: (Show? a, Typeable? a) -> a -> String
showAny (Just QED, _) a = show a
showAny (_, Just QED) a = "_ :: " ++ showType a
showAny _ _ = "_"
哪种方法效果很好:
>>> showAny (Nothing, Just QED) (id :: Int -> Int)
"_ :: Int -> Int"
>>> showAny (Just QED, Just QED) (0 :: Int)
"0"
>>> showAny (Nothing, Nothing) undefined
"_"