2016-03-03 12 views
6

Załóżmy, że mam prosty typ danych w Haskell do przechowywania wartości:Sprawdź, czy typ jest wystąpieniem programu Show w programie Haskell?

data V a = V a 

chcę zrobić V wystąpienie Pokazują, bez względu na typ. Jeśli a jest instancją programu Show, to show (V a) powinien zwrócić show a, w przeciwnym razie powinien zostać zwrócony komunikat o błędzie. Lub w Pseudo-Haskell:

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

Jak to zachowanie można zastosować w Haskell?

+4

Haskell jest w pełni wymazanym językiem; w czasie wykonywania struktury alokowane w pamięci nie zawierają znaczników, które można wykorzystać do odzyskania ich typów lub klas, które te typy implementują. Dlatego nie ma operatora 'instanceof' takiego jak Java lub podobne języki. (Istnieją bardziej zaawansowane techniki, które mogą być używane w niektórych przypadkach do podobnego typu refleksji typu runtime, ale jeśli jesteś początkującym, powinieneś najpierw trzymać się podstaw!) –

+7

To zachowanie nie może zostać zaimplementowane, ponieważ filozoficznie, każdy typ jest w każdej klasie_.Przyznane, jeśli kompilator nie może _find_ instancji 'Show' dla jakiegoś typu spróbujesz' show', wystąpi błąd; ale rozumiane jest to konceptualnie "zapomniałeś napisać niezbędną instancję" zamiast "spróbowałeś pokazać typ, który _nie jest_przygotowalny". Klasy typów są otwarte, każdy może później zdefiniować wystąpienia dla niektórych klas bibliotecznych. Kompilator nie może udowodnić, że tak się nie stanie, gdy skompiluje bibliotekę! – leftaroundabout

+0

To powiedziawszy: zachowanie można emulować za pomocą [nakładających się instancji] (https://downloads.haskell.org/~ghc/7.8.1/docs/html/users_guide/type-class-extensions.html#instance-overlap) , które są uważane za brzydkie (lub nawet niebezpieczne). Być może lepiej pasuje do tego idea [rodziny typu zamkniętego] (http://research.microsoft.com/en-us/um/people/simonpj/papers/ext-f/axioms-extended.pdf), choć nie łatwo możesz zaimplementować tę instancję pokazową. – leftaroundabout

Odpowiedz

10

Tak jak napisałem w komentarzu, obiekty środowiska wykonawczego przydzielone w pamięci nie mają znaczników typów w programie Haskell. Dlatego nie ma uniwersalnej operacji instanceof, jak na przykład Java.

Należy również wziąć pod uwagę następstwa. W Haskell, do pierwszego przybliżenia (tj. Zignorowania pewnych wymyślnych rzeczy, których początkujący nie powinni za szybko rozwiązywać), wszystkie wywołania funkcji wykonawczych są następujące: monomorficzna. Oznacza to, że kompilator zna, bezpośrednio lub pośrednio, monomorficzny (nietypowy) typ każdego wywołania funkcji w wykonywalnym programie. Chociaż show funkcja Twojego V Type zawiera typ rodzajowy:

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

... nie można właściwie napisać program, który wywołuje funkcję w czasie wykonywania bez, bezpośrednio lub pośrednio, informując kompilator dokładnie, jaki rodzaj będzie a być w każdym telefonie. Tak więc na przykład:

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

widziany w tym świetle, mam nadzieję, że można dostrzec problem z co pytasz:

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

Zasadniczo, gdyż typ parametru a znana będzie w momencie kompilacji czas rzeczywistego wywołania funkcji show, nie ma potrzeby testowania tego typu w czasie wykonywania - możesz go przetestować w czasie kompilacji! Po zrozumieć to, jesteś doprowadziły do ​​sugestie będą Sewell za:

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

EDIT: bardziej konstruktywną odpowiedź być może być w ten sposób: Twój typ V musi być rekord z wariantami wielu przypadkach. To wymaga używając rozszerzenia 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." 

Ale nie jest to check runtime, czy dany typ jest Show instancji kod jest odpowiedzialny za znajomość w czasie kompilacji, gdzie konstruktor Showable ma być używany.

+0

Co, jeśli, z jakiegoś powodu, naprawdę * chcesz zwrócić komunikat o błędzie dla typów innych niż Pokaż? – immibis

+0

@immibis: Teraz, gdy o tym myślę, możesz użyć ostatniej wersji z ograniczeniem "Pokaż a" i [opcja kompilatora odroczyła błędy typu do czasu wykonania] (https://downloads.haskell.org/~ghc /latest/docs/html/users_guide/defer-type-errors.html). Ale kompilator nadal będzie ostrzegał, że twój program może zawieść w czasie działania - powie ci nawet, które linie mogłyby zawieść! –

+0

* Zwróć * komunikat o błędzie, nie zawieszaj programu za pomocą jednego. – immibis

2

Nawet gdybyś mógł to zrobić, byłby to zły projekt. Polecam dodanie Show ograniczenie do a:

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

Jeśli chcesz przechowywać członków w danych typu kontenerów, które nie są instancją Show, to należy utworzyć nowy typ danych dziobowy nich.

9

Możesz za pomocą tej biblioteki: https://github.com/mikeizbicki/ifcxt.Możliwość wywoływania show na wartości, która może lub nie może mieć instancji Show, jest jednym z pierwszych podanych przez nią przykładów. W ten sposób można dostosować, że dla 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>>" 

To jest istota tej biblioteki:

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

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

nie w pełni go zrozumieć, ale to jest, jak myślę, działa:

nie narusza „otwartego świata” założenie więcej niż

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

ma. Podejście jest podobne do tego: dodanie domyślnego przypadku do wszystkich typów, które nie mają instancji w zakresie. Jednak dodaje trochę pośrednictwa, aby nie zepsuć istniejących instancji (i pozwolić różnym funkcjom na określenie różnych wartości domyślnych). IfCxt pracuje jako AA „meta-klasie”, klasy o ograniczeniach, która wskazuje, czy istnieją takie przypadki, z domyślnym przypadku, który wskazuje „false”.:

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

Wykorzystuje TemplateHaskell wygenerować długą listę przypadków dla tej klasy:

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

co oznacza również, że wszelkie przypadki, które nie były w zakresie gdy mkIfCxtInstances nazwano będą rozpatrywane nieistniejącą.

proxy cxt Argument ten jest używany do przekazywania do Constraint do funkcji, (cxt => a) argument (ja nie RankNTypes idea pozostawiono iż) jest teza, że ​​można zastosowanie ograniczenie cxt, ale tak długo, jak argument jest nieużywany, ograniczenie nie musi być rozwiązane. Jest to podobne do:

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

proxy argumentu dostarcza ograniczenie, a następnie IfCxt ograniczeniem jest rozwiązany albo argumentu t lub f, czy to t następnie istnieją pewne IfCxt instancja gdzie to ograniczenie jest dostarczana co oznacza, że można rozwiązać bezpośrednio, jeśli jest to f, to ograniczenie nigdy nie jest wymagane, więc zostaje zrzucone.


To rozwiązanie jest niedoskonały (jak nowe moduły mogą definiować nowe Show instancji, który nie będzie działać, chyba że wymaga również mkIfCxtInstances), ale jest w stanie to zrobić by naruszać otwartym światem założenie.

Powiązane problemy