2016-03-03 124 views
6

假設我在Haskell有一個簡單的數據類型存儲的值:檢查一個類型是否是Haskell中的Show實例?

data V a = V a 

我想使V展會的一個實例,不管的類型。如果a是Show的一個實例,則show (V a)應返回show a,否則應返回錯誤消息。或者在僞Haskell中:

instance Show (V a) where 
    show (V a) = if a instanceof Show 
        then show a 
        else "Some Error." 

這種行爲在Haskell中如何實現?

+4

Haskell是一種完全刪除類型的語言;在運行時,內存分配的結構不包含可用於恢復其類型或這些類型實現的類的標籤。因此,沒有Java或類似語言提供的'instanceof'運算符。 (有些更高級的技術可以在某些情況下用於類似的運行時類型反射,但如果您是初學者,您應該首先堅持基本知識!) –

+7

此行爲無法實現,因爲哲學上_every類型是在每個班級。當然,如果編譯器無法爲某種類型的'Show'實例查找'show',則會出現錯誤;但這被理解爲在概念上「你忘了寫必要的實例」,而不是「你試圖展示一個不可顯示的類型」。類型類是開放的,任何人都可以稍後爲某些庫類定義實例。當編譯庫時,編譯器不可能證明這不會發生! – leftaroundabout

+0

也就是說:行爲可以用[重疊實例]來模擬(https://downloads.haskell.org/~ghc/7.8.1/docs/html/users_guide/type-class-extensions.html#instance-overlap) ,這被認爲有點醜陋(甚至不安全)。也許更好地匹配這個想法是[封閉類型家族](http://research.microsoft.com/en-us/um/people/simonpj/papers/ext-f/axioms-extended.pdf),雖然他們不很容易讓你實現這個show實例。 – leftaroundabout

回答

10

正如我在評論中所述,在內存中分配的運行時對象在Haskell程序中沒有類型標記。因此,沒有像Java中那樣的通用instanceof操作。

考慮以下內容也很重要。在Haskell中,對於初步近似(即忽略一些初學者不應該過早處理的奇特東西),所有運行時函數調用都是單態。也就是說,編譯器直接或間接地知道可執行程序中每個函數調用的單態(非泛型)類型。即使你的V類型的show函數有一個泛型類型:

-- Specialized to `V a` 
show :: V a -> String -- generic; has variable `a` 

...你不能真正寫在運行時調用該函數沒有程序的情況,直接或間接地告訴編譯器到底是什麼類型的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 

由此看來,希望你可以發現你要問什麼問題:

instance Show (V a) where 
    show (V a) = if a instanceof Show 
        then show a 
        else "Some Error." 

基本上,因爲對於a參數類型將在編譯知道任何實際調用show函數的時間,都沒有必要在運行時測試這種類型 - 您可以在編譯時測試它!一旦你掌握這個,你就導致了威爾休厄爾的建議:

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

編輯:更具建設性的答案也許可能是這樣的:你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." 

但是,這不是一個類型是否爲Show實例,你的代碼是負責瞭解在編譯時在Showable構造函數是要使用的運行時檢查。

+0

如果出於某種原因,您真的*想要爲非顯示類型返回錯誤信息,該怎麼辦? – immibis

+0

@immibis:現在我想起來了,你可以使用帶Show''約束的最新版本,[編譯器選項將錯誤類型推遲到運行時](https://downloads.haskell.org/~ghc /latest/docs/html/users_guide/defer-type-errors.html)。但是編譯器仍然會警告你,你的程序在運行時可能會失敗 - 它甚至會告訴你究竟哪一行會失敗! –

+0

*返回*錯誤消息,不會使程序崩潰。 – immibis

2

即使你可以這樣做,這將是一個糟糕的設計。我會建議增加一個Show約束a

instance Show a => Show (V a) where ... 

如果要存儲成員不在的Show的實例的容器數據類型,那麼你應該創建一個新的數據類型前他們。

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>>" 

這是該庫的精髓:

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

instance {-# OVERLAPPABLE #-} IfCxt cxt where ifCxt _ t f = f 

我不完全瞭解,但是這是我怎麼想它工作原理:

這並不違反「開放的世界」的假設任何超過

instance {-# OVERLAPPABLE #-} Show a where 
    show _ = "<<unshowable>>" 

確實。該方法實際上非常類似於此:添加一個默認情況以回溯所有沒有範圍實例的類型。但是,它增加了一些間接性,以避免混淆現有實例(並允許不同的函數指定不同的默認值)。 IfCxt作品AA「元級」,對限制類,指示這些實例是否存在,與表示默認情況下「假」:

instance {-# OVERLAPPABLE #-} IfCxt cxt where ifCxt _ t f = f 

它使用TemplateHaskell生成實例的一個長長的清單該類:

instance {-# OVERLAPS #-} IfCxt (Show Int) where ifCxt _ t f = t 
instance {-# OVERLAPS #-} IfCxt (Show Char) where ifCxt _ t f = t 

這也意味着,這是不在範圍內時mkIfCxtInstances被稱爲將被視爲不存在任何實例。

proxy cxt參數用於一個Constraint傳遞給函數時,(cxt => a)參數(我不知道RankNTypes允許這)是一個參數,可以使用約束cxt,但只要這樣的說法是不使用的,約束不需要解決。這是類似的:

f :: (Show (a -> a) => a) -> a -> a 
f _ x = x 

proxy參數提供的約束,那麼IfCxt約束求解,無論是tf說法,如果它是t再有就是一些IfCxt實例,其中該約束提供,這意味着它可以直接解決,如果它是f那麼約束從不要求,因此它被丟棄。


該解決方案是不完善的(如新的模塊可以定義新Show情況下,這是行不通的,除非它也要求mkIfCxtInstances),但能夠做到這一點違反了開放世界的假設。

相關問題