2012-02-24 8 views
5

Questa è una domanda successiva a this one. Penso di aver frainteso un po 'che tipo di didascalia ha intenzione di fare in Haskell, quindi ecco, si spera, una migliore formulazione della domanda:Scrivere una funzione generica con esattamente due varianti di tipi di parametri

Voglio avere una funzione che può essere chiamata con esattamente due argomenti. Questi argomenti devono essere di tipi diversi. Ad esempio, uno è una stringa e un altro è un numero intero.

Considerate questa applicazione:

combine "100" 500 -- results in 100500 
combine 100 "500" -- results in 100500 
combine 100 500 -- raises an exception 
combine "100" "500" -- raises an exception 

Non è un problema di scrivere una concreta attuazione, si tratta di un problema, tuttavia, per me, per dare questa funzione una firma corretta.

Sarei anche interessato a sapere se esiste una soluzione più generica (ovvero non è necessario specificare i tipi concreti, ma solo i tipi per essere diversi. In modo che, ad esempio, si possa utilizzare questa funzione per "fissare" l'ingresso ad altre funzioni, se può essere fissato permutando gli argomenti

Grazie

EDIT:.!

di seguito è una copia imprecisa di quello che mi aspettavo da fare in Erlang ... beh, spero che abbia senso, visto che dovrebbe essere abbastanza simile ...

combine([String], Int)-> 
    io:fwrite("~s~w~n", [[String], Int]); 

combine(Int, [String])-> 
    combine([String], Int). 
+3

Poiché si tratta di un problema con i tipi, che non vorresti a "sollevare un'eccezione", che ci si vuole un errore di compilazione. La differenza può sembrare pedante, ma è importante. In Haskell, vogliamo sempre gli errori in fase di compilazione, se possibile.Inoltre, sembra che tu stia pensando in Python invece di Haskell: P –

+3

Direi che il modo giusto per farlo è scrivere: 'combine :: Integer -> String -> String'. Ciò garantisce che prenderà esattamente un argomento di ciascun tipo. È semplice, è chiaro. Non comporta alcuna funzionalità avanzata. Cosa si potrebbe chiedere di più? – Carl

+0

Personalmente raccomanderei semplicemente di fare due funzioni: 'combineStrInt :: String -> Int -> Int' e' combineIntStr :: Int -> String -> Int'. Io * vorrei * raccomandare solo una funzione, e poi usare 'flip' per invertire l'ordine degli argomenti quando necessario, ma per il tuo esempio, semplicemente capovolgere i parametri non produrrebbe il risultato corretto. In pratica, probabilmente troverai 'flip' utile. –

risposta

7

Sjoerd bastonami , ma preferisco la mia soluzione quindi la pubblicherò comunque.

{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances, FlexibleInstances #-} 

module Foo where 

class Combinable a b where 
    combine :: a -> b -> Int 

instance Combinable Int String where 
    combine a b = read (show a ++ b) 

instance Combinable String Int where 
    combine a b = read (a ++ show b) 

Dal momento che questo non include un'istanza Combinable a a, cercando di utilizzare uno è un errore di compilazione, piuttosto che un errore di runtime.

3

Sono quasi sicuro che non puoi fare la cosa generica che descrivi. In Haskell non c'è modo di esprimere negazioni di uguaglianze come tu descrivi.

Si potrebbe essere in grado di fare qualche trucco molto sporco usando OverlappingInstances e classi di tipo multi-parametro, che risulterebbero in errori di runtime invece di errori in fase di compilazione, ma sarebbe davvero brutto e deprimente.

+0

+1 per brutto e deprimente. –

+0

In realtà, è possibile applicare l'ineguaglianza di tipo in Haskell tramite classi di tipo multiparametro/dipendenze di funzioni/etc e ottenere errori nella compilazione del tempo. "Oleg l'ha già fatto". Un'enorme quantità di programmi di livello di tipo si basa su questo. Per quanto ne so, questo è l'unico tipo di vincolo negativo che può essere applicato. Ovviamente è anche una violazione non valida di OWS se combinata con famiglie di tipi. –

1

È necessario creare un proprio tipo di dati perché non è possibile avere tipi non definiti in haskell.

data IntAndString = TypeA Int String | TypeB String Int 

combine IntAndString -> string 
combine TypeA(n s) = show n ++ s 
combine TypeB(s n) = s ++ show n 

combinare lattina Eiter essere chiamato con

combine TypeA(Int String) 

o

combine TypeB(String Int) 
+0

In aumento perché mi piace la soluzione, ma cosa vuoi dire che Haskell non può avere tipi generici? –

+1

Beh, quello era sbagliato da parte mia. I tipi possono essere genirici, ma le funzioni non possono essere generiche – nist

3

La soluzione brutto e deprimente Louis Wasserman menzionato sarebbe qualcosa di simile:

{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances, FlexibleInstances #-} 

class Combine a b where 
    combine :: a -> b -> String 

instance Combine a a where 
    combine = error "Types are the same" 

instance (Show a, Show b) => Combine a b where 
    combine a b = show a ++ show b 
+0

Probabilmente avrai bisogno di OverlappingInstances per fare in modo che GHC lo ingoia. –

+2

@LouisWasserman Sorprendentemente no. A meno che non lo si usi in una situazione in cui _might_ usa 'combine :: a -> a -> String'. Il controllo di sovrapposizione di GHC è pigro, non si attiva fino a quando non è necessario. –

+0

Le nostre risposte sono essenzialmente le stesse; l'unica differenza sono le istanze che abbiamo scelto di definire. Molti Haskellers hanno un'avversione per scrivere un gran numero di decli di istanze (il mio approccio porterà ad un'esplosione di declaggi se inizi ad aggiungere altri tipi), ma IMHO a volte è il modo migliore. –

1

Non è un problema scrivere un'implementazione concreta, è un problema , tuttavia, per me, dare a questa funzione una firma appropriata.

Fintanto che le funzioni non hanno tipi di classificazione più elevati, non è necessario. Haskell dedurrà il tipo per te.

Detto questo, sento che quello che vuoi non ha molto senso in Haskell, dove codice e dati sono strettamente separati1 così come tempo di esecuzione e tempo di compilazione, a differenza di Lisp. Quale sarebbe un caso d'uso per combine?

¹ Ovviamente le funzioni sono dati in un certo senso, ma sono solo costanti completamente opache. Non è possibile manipolare una funzione in fase di esecuzione.

+1

È facile: non è così. Non esiste una spedizione multipla in Haskell. – Ingo

6

Non è al 100% chiaro per me perché lo vuoi questo. Una possibilità che mi è venuta in mente che altri non abbiano menzionato è che si desidera semplicemente un'applicazione della funzione agnostica per ordine. Questo è possibile con l'idioma "applicazione record". Ad esempio, si potrebbe scrivere qualcosa del genere:

data Argument = Argument { name :: String, age :: Int } 
instance Default Argument where def = Argument def def 

combine Argument { name = n, age = a } = name ++ " is " ++ show age ++ " years old" 

È quindi possibile chiamare con argomenti con nome:

combine def { name = "Daniel", age = 3 } 
combine def { age = 3, name = "Daniel" } 

nomi sono anche solo un po 'meglio di verificare che i tipi non sono uguali, perché puoi avere più argomenti con lo stesso tipo senza ambiguità.

data Name = Name { first, middle, last :: String } 
instance Default Name where def = Name def def def 

esquire [email protected](Name { last = l }) = n { last = l ++ ", Esquire" } 

Che si può chiamare come questi due, per esempio:

esquire def { first = "Daniel", middle = "M.", last = "Wagner" } 
esquire def { last = "Wagner", first = "Daniel" } 
6

Mentre le altre risposte sono "scrivere un (leggermente brutto) di classe" e "unificare i tipi tramite un tipo sum". Farò un suggerimento non molto Haskelly e ricorderò a tutti che Haskell ha una digitazione dinamica se lo chiedi.

In fase di esecuzione, basta chiedere che tipo è e rendere l'operazione diversa per ogni tipo. Questo può essere fatto usando il modulo Data.Typeable.

Ad esempio:

import Data.Typeable 
import Data.Data 

combine :: (Typeable a, Typeable b) => a -> b -> Int 
combine a b 
    | typeOf a == strTy && typeOf b == intTy = 
     case (cast a, cast b) of 
      (Just str,Just i) -> read $ str ++ show (i :: Int) 
    | typeOf a == intTy && typeOf b == strTy = 
     case (cast a, cast b) of 
      (Just i,Just str) -> read $ show (i :: Int) ++ str 
    | otherwise = error "You said you wanted an exception..." 
where 
strTy = typeOf "" 
intTy = typeOf (undefined :: Int) 

E una corsa di prova mostra:

> combine "100" (500 :: Int) 
100500 

Se si vuole sbarazzarsi di eccezione allora grande! Siamo in grado di ripulire il codice utilizzando il Forse monade, mentre che ci siamo:

combine2 :: (Typeable a, Typeable b) => a -> b -> Maybe Int 
combine2 a b 
    | typeOf a == strTy && typeOf b == intTy = do 
     a' <- cast a 
     b' <- cast b 
     return $ read $ a' ++ show (b' :: Int) 
    | typeOf a == intTy && typeOf b == strTy = do 
     a' <- cast a 
     b' <- cast b 
     return $ read $ show (a' :: Int) ++ b' 
    | otherwise = Nothing 
where 
strTy = typeOf "" 
intTy = typeOf (undefined :: Int) 

E ancora un po 'di uscita solo per il gusto di farlo:

> combine2 "500" (5 :: Int) 
Just 5005 
> combine (5 :: Int) "500" 
5500 
> combine2 (5 :: Int) "500" 
Just 5500 
> combine "500" "300" 
*** Exception: You said you wanted an exception... 
> combine2 "500" "300" 
Nothing 

E il gioco è fatto! Possiamo aggiungere come mai molte combinazioni di tipi che vogliamo, basta inserire le operazioni desiderate prima dell'ultima guardia otherwise.

2

Un'altra soluzione brutta e deprimente:

{-# FlexibleInstances, TypeSynonymInstances #-} 

class IntOrString a where 
    toString :: a -> String 
    typeID :: a -> Int 

instance IntOrString String where 
    toString s = s 
    typeID _ = 0  

instance IntOrString Int where 
    toString x = show x 
    typeID _ = 1  

combine a b | typeID a + typeID b == 1 = toString a ++ toString b 
combine _ _ = error "WTF?!?" 

combine "100" (500::Int) --type needed because of monomorphism restriction 
Problemi correlati