2016-03-03 4 views
6

Supponiamo che io sono un semplice tipo di dati in Haskell per la memorizzazione di un valore:Controlla se un tipo è un'istanza di Mostra in Haskell?

data V a = V a 

Voglio fare V un'istanza di spettacolo, a prescindere di un tipo. Se a è un'istanza di Show, quindi deve restituire show a altrimenti è necessario restituire un messaggio di errore. O in Pseudo-Haskell:

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

Come potrebbe essere implementato questo comportamento in Haskell?

+4

Haskell è un linguaggio completamente cancellato dal testo; in fase di esecuzione, le strutture allocate alla memoria non contengono tag che possono essere utilizzati per recuperare i loro tipi o le classi implementate da questi tipi. Pertanto, non esiste un operatore 'instanceof' come Java o lingue simili. (Esistono tecniche più avanzate che possono essere utilizzate in alcuni casi per la riflessione del tipo di runtime simile, ma se sei un principiante devi prima attenersi alle nozioni di base!) –

+7

Questo comportamento non può essere implementato, perché filosoficamente, il tipo _every è in ogni classe_.Certo, se il compilatore non può _find_ un'istanza 'Show' per qualche tipo si prova a' show', ci sarà un errore; ma questo è inteso come concettualmente "hai dimenticato di scrivere l'istanza necessaria" invece di "hai provato a mostrare un tipo che non è _ visibile". Le classi di tipi sono aperte, in seguito chiunque può definire le istanze per alcune classi di libreria. Il compilatore non può provare che ciò non accadrà, quando compila la libreria! – leftaroundabout

+0

Detto questo: il comportamento può essere emulato con [istanze sovrapposte] (https://downloads.haskell.org/~ghc/7.8.1/docs/html/users_guide/type-class-extensions.html#instance-overlap) , che sono considerati un trucco un po 'brutto (o addirittura pericoloso). Forse meglio abbinare l'idea sono [famiglie di tipo chiuso] (http://research.microsoft.com/en-us/um/people/simonpj/papers/ext-f/axioms-extended.pdf), anche se non lo fanno prontamente ti permettono di implementare quella mostra. – leftaroundabout

risposta

10

Come ho detto in un commento, gli oggetti di runtime allocati in memoria non hanno tag di tipo in un programma Haskell. Non esiste quindi l'operazione universale instanceof come, ad esempio, in Java.

È anche importante considerare le implicazioni di quanto segue. In Haskell, in prima approssimazione (ad esempio, ignorando alcune cose di fantasia che i principianti non dovrebbero affrontare troppo presto), tutte le chiamate di funzioni di runtime sono monomorphic. Ad esempio, il compilatore conosce, direttamente o indirettamente, il tipo monomorfico (non generico) di ogni chiamata di funzione in un programma eseguibile. Anche se la funzione del tipo Vshow ha un tipo generico:

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

... non si può effettivamente scrivere un programma che chiama la funzione in fase di runtime, senza, direttamente o indirettamente, dicendo al compilatore esattamente che tipo a volontà essere in ogni singola chiamata. Così, per esempio:

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

Visto in questa luce, si spera è possibile individuare il problema con quello che stai chiedendo:

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

In sostanza, dal momento che il tipo per il parametro a sarà conosciuto in compilation tempo per ogni chiamata effettiva alla funzione show, non c'è motivo di testare questo tipo in fase di esecuzione: è possibile verificarlo al momento della compilazione! Una volta che si comprende questo, si sta portato al suggerimento di Will Sewell:

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

EDIT: Una risposta più costruttiva forse potrebbe essere questo: il tipo di V deve essere un'unione tag di molteplici casi. Ciò richiede utilizzando l'estensione 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." 

Ma questo non è un assegno di runtime di se un tipo è un Show istanza il codice è responsabile di conoscere al momento della compilazione in cui il costruttore Showable deve essere utilizzato.

+0

E se, per qualche ragione, * avessi davvero * voluto restituire un messaggio di errore per i tipi non-Show? – immibis

+0

@immibis: Ora che ci penso, potresti usare l'ultima versione con il vincolo 'Show a', e [l'opzione del compilatore per rinviare gli errori di tipo al runtime] (https://downloads.haskell.org/~ghc /latest/docs/html/users_guide/defer-type-errors.html). Ma il compilatore ti avvertirà comunque che il tuo programma potrebbe fallire in fase di esecuzione: ti dirà anche quali linee potrebbero fallire! –

+0

* return * un messaggio di errore, non crash il programma con uno. – immibis

2

Anche se fosse possibile farlo, sarebbe un cattivo progetto. Mi sento di raccomandare l'aggiunta di un vincolo Show-a:

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

Se si desidera memorizzare i membri in un tipo di dati contenitore che non sono un caso di Show, allora si dovrebbe creare un nuovo tipo di dati in primo piano loro.

9

È possibile con questa libreria: https://github.com/mikeizbicki/ifcxt.Essere in grado di chiamare show su un valore che può o non può avere un'istanza Show è uno dei primi esempi che fornisce. Questo è come si potrebbe adattare che per 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>>" 

Questa è l'essenza di questa libreria:

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

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

io non lo comprendiamo pienamente, ma questo è come penso che funziona:

Essa non viola l'ipotesi "mondo aperto" più di quanto

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

fa. L'approccio è in realtà molto simile a questo: aggiungere un caso predefinito a cui ricorrere per tutti i tipi che non hanno un'istanza in ambito. Tuttavia, aggiunge qualche riferimento indiretto per non creare confusione con le istanze esistenti (e per consentire a diverse funzioni di specificare valori predefiniti diversi). IfCxt opere come aa "meta-class", una classe su vincoli, che indica se esistano questi casi, con un caso di default che indica "false.":

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

utilizza TemplateHaskell per generare un lungo elenco di istanze per quella classe:

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

che implica anche che tutte le istanze che non erano di portata, quando è stato chiamato mkIfCxtInstances sarà considerato inesistente.

L'argomento proxy cxt viene utilizzato per passare un Constraint alla funzione, l'argomento (cxt => a) (non avevo idea RankNTypes consentito che) è un argomento che può uso il vincolo cxt, ma finché tale argomento è inutilizzato, il vincolo non ha bisogno di essere risolto. Questo è simile a:

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

L'argomento proxy fornisce il vincolo, quindi il vincolo IfCxt è risolto sia alla tesi t o f, se è t allora c'è qualche IfCxt caso in cui questo vincolo viene fornito il che significa che può essere risolto direttamente, se è f quindi il vincolo non viene mai richiesto, quindi viene eliminato.


Questa soluzione è imperfetta (come nuovi moduli possono definire nuovi Show casi che non funzioneranno a meno che non chiede anche mkIfCxtInstances), ma essere in grado di farlo avrebbe violare il presupposto mondo aperto.