2012-02-24 15 views
5

Esta es una pregunta de seguimiento al this one. Creo que malentendí un poco qué tipo se debe hacer en Haskell, así que aquí está, con suerte, una mejor formulación de la pregunta:Escriba una función genérica con exactamente dos variaciones de tipos de parámetros

Quiero tener una función que se pueda invocar con exactamente dos argumentos. Estos argumentos deben ser de diferentes tipos. Por ejemplo, uno es cadena, y otro es un número entero.

considerar esta aplicación:

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

No es un problema para escribir una aplicación concreta, se trata de un problema, sin embargo, para mí, para dar a esta función de una firma apropiada.

También me interesaría saber si existe una solución que sea más genérica (es decir, no requiera especificar los tipos concretos, sino que solo prescriba que los tipos sean diferentes. De modo que, por ejemplo, podría utilizar esta función para "fijar" la entrada a otras funciones, si se puede solucionar mediante la permutación de los argumentos

Gracias

EDIT:.!

a continuación es una copia imprecisa de lo que estaba esperando Que hacer en Erlang ... bueno, espero que tenga sentido, ya que debería ser bastante similar ...

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

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

Ya que es un problema con los tipos, que no quiere "lanzar una excepción", que querría un error en tiempo de compilación. La diferencia puede sonar pedante, pero es importante. En Haskell, siempre queremos errores en tiempo de compilación si es posible.Además, parece que estás pensando en Python en lugar de Haskell: P –

+3

Diría que la forma correcta de hacerlo es escribir: 'combine :: Integer -> String -> String'. Eso garantiza que tomará exactamente un argumento de cada tipo. Es simple, está claro. No implica ninguna característica avanzada. ¿Qué más podrías pedir? – Carl

+0

Yo personalmente recomendaría simplemente hacer dos funciones: 'combineStrInt :: String -> Int -> Int' y' combineIntStr :: Int -> String -> Int'. Yo * recomendaría * solo una función, y luego usar 'flip' para invertir el orden de los argumentos cuando sea necesario, pero para su ejemplo, simplemente voltear los parámetros no produciría el resultado correcto. En la práctica, probablemente encontrarás 'flip' útil. –

Respuesta

7

Sjoerd vencerme , pero prefiero mi solución, así que la publicaré de todos modos.

{-# 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) 

Dado que esto no incluye una instancia de Combinable a a, tratando de utilizar uno es un error en tiempo de compilación en lugar de un error de ejecución.

3

Estoy casi seguro de que no puedes hacer lo genérico que describes. No hay forma en Haskell de expresar negaciones de igualdad como describes.

es posible que pueda hacer algún artilugio muy sucio utilizando OverlappingInstances y clases de tipo de múltiples parámetros, que daría lugar a errores de ejecución en lugar de errores en tiempo de compilación, pero eso sería muy feo y deprimente.

+0

+1 para feo y deprimente. –

+0

En realidad, puede aplicar la desigualdad de tipo en Haskell a través de clases/dependencias de función/etc de tipos de parámetros múltiples y obtener errores de tiempo de compilación. "Oleg ya lo hizo". Una gran cantidad de programación de nivel de tipo se basa en esto. Hasta donde yo sé, este es el único tipo de restricción negativa que se puede aplicar. También es obviamente una violación poco sólida del OWS cuando se combina con familias de tipos. –

1

Tienes que crear un tipo de datos propio porque no puedes tener tipos indefinidos en 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 

combinar eiter puede ser llamado con

combine TypeA(Int String) 

o

combine TypeB(String Int) 
+0

Upvoted porque me gusta la solución, pero ¿qué quiere decir con que Haskell no puede tener tipos genéricos? –

+1

Bueno, eso fue mal por mí. Los tipos pueden ser genéricos, pero las funciones no pueden ser genéricas. – nist

3

La solución fea y deprimente Louis Wasserman mencionado sería algo como esto:

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

Probablemente necesites OverlappingInstances para que GHC lo trague. –

+2

@LouisWasserman Sorprendentemente no es así. A menos que lo use en una situación en la que _might_ use 'combine :: a -> a -> String'. El control de superposición de GHC es flojo, no se dispara hasta que sea necesario. –

+0

Nuestras respuestas son esencialmente las mismas; la única diferencia son las instancias que elegimos definir. Muchos Haskeller tienen una aversión a escribir un gran número de decls de instancia (mi enfoque dará lugar a una explosión de decls si comienzas a agregar más tipos), pero en mi humilde opinión a veces es la mejor manera. –

1

No es un problema escribir una implementación concreta, es un problema , sin embargo, para mí, para darle a esta función una firma apropiada.

Siempre que las funciones no tengan tipos de mayor clasificación, no es necesario. Haskell inferirá el tipo para ti.

Dicho esto, creo que lo que quiere no tiene mucho sentido en Haskell, donde el código y los datos están estrictamente separados¹, así como el tiempo de ejecución y compilación, a diferencia de Lisp. ¿Cuál sería un caso de uso para combine?

¹ Por supuesto, las funciones son datos en cierto sentido, pero son solo constantes totalmente opacas. No puede manipular una función en tiempo de ejecución.

+1

Eso es fácil: no es así. No existe el despacho múltiple en Haskell. – Ingo

6

No es 100% claro para mí por qué desea esto. Una posibilidad que se me ocurrió que otros no habían mencionado es que simplemente quieres la aplicación de función agnóstica de orden. Esto es posible con la expresión "aplicación de registro". Por ejemplo, podría escribir algo como esto:

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" 

A continuación, puede llamarla con argumentos con nombre:

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

nombres son incluso sólo un poco mejor que comprobar que los tipos no son iguales, porque puede tener múltiples argumentos con el mismo tipo sin ambigüedad.

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

que se puede llamar como estos dos, por ejemplo:

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

Mientras que las otras respuestas son "escribir una clase (un poco feo)" y "unificar los tipos a través de un tipo de suma". Voy a hacer una sugerencia que no sea muy Haskelly y recordarles a todos que Haskell tiene una escritura dinámica si la pides.

En tiempo de ejecución, simplemente PREGUNTE qué tipo es y haga que su operación sea diferente para cada tipo. Esto se puede hacer usando el módulo Data.Typeable.

Por ejemplo:

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) 

Y una prueba muestra:

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

Si quieren deshacerse de la excepción, entonces genial! Podemos limpiar el código utilizando la mónada Tal vez mientras estamos en ello:

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) 

Y poco mas de salida sólo por el gusto de hacerlo:

> 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 

Y eso es todo! Podemos agregar cuantas combinaciones de tipos deseemos, solo inserte sus operaciones deseadas antes de la última protección otherwise.

2

Otra solución fea y 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 
Cuestiones relacionadas