programar - ¿Puedes sobrecargar+en Haskell?
programar en haskell (5)
Si bien he visto todo tipo de cosas raras en el código de muestra de Haskell, nunca he visto a un operador más estar sobrecargado. ¿Hay algo especial al respecto?
Digamos que tengo un tipo como Pair , y quiero tener algo como
Pair(2,4) + Pair(1,2) = Pair(3,6)
¿Puede uno hacerlo en Haskell?
Solo tengo curiosidad, ya que sé que es posible en Scala de una manera bastante elegante.
Sí
(+)
es parte de la clase de tipo Num
, y todos parecen sentir que no puedes definir (*)
etc. para tu tipo, pero estoy totalmente en desacuerdo.
newtype Pair a b = Pair (a,b) deriving (Eq,Show)
Creo que Pair ab
sería más agradable, o podríamos simplemente usar el tipo (a,b)
directamente, pero ...
Esto es muy parecido al producto cartesiano de dos Monoids, grupos, anillos o lo que sea en matemáticas, y hay una forma estándar de definir una estructura numérica, que sería sensato de usar.
instance (Num a,Num b) => Num (Pair a b) where
Pair (a,b) + Pair (c,d) = Pair (a+c,b+d)
Pair (a,b) * Pair (c,d) = Pair (a*c,b*d)
Pair (a,b) - Pair (c,d) = Pair (a-c,b-d)
abs (Pair (a,b)) = Pair (abs a, abs b)
signum (Pair (a,b)) = Pair (signum a, signum b)
fromInteger i = Pair (fromInteger i, fromInteger i)
Ahora hemos sobrecargado (+)
de una manera obvia, pero también nos hemos salido todos los cerdos y hemos estado sobrecargados (+)
(*)
y todas las demás funciones de Num
de la misma manera obvia y familiar en que las matemáticas lo hacen por un par. Simplemente no veo el problema con esto. De hecho, creo que es una buena práctica.
*Main> Pair (3,4.0) + Pair (7, 10.5)
Pair (10,14.5)
*Main> Pair (3,4.0) + 1 -- *
Pair (4,5.0)
*
- Tenga en cuenta que fromInteger
se aplica a literales numéricos como 1
, por lo que esto se interpretó en ese contexto como Pair (1,1.0) :: Pair Integer Double
. Esto también es bastante agradable y útil.
La sobrecarga en Haskell es posible a través de clases de tipos. Para obtener una buena descripción general, es posible que desee consultar esta sección en Learn You a Haskell .
El operador (+)
es parte de la clase de tipo Num
del Prelude :
class (Eq a, Show a) => Num a where
(+), (*), (-) :: a -> a -> a
negate :: a -> a
...
Por lo tanto, si desea una definición para +
trabajar para pares, debe proporcionar una instancia.
Si tienes un tipo:
data Pair a = Pair (a, a) deriving (Show, Eq)
Entonces podrías tener una definición como:
instance Num a => Num (Pair a) where
Pair (x, y) + Pair (u, v) = Pair (x+u, y+v)
...
Perforar esto en ghci
nos da:
*Main> Pair (1, 2) + Pair (3, 4)
Pair (4,6)
Sin embargo, si va a dar una instancia para +
, también debería proporcionar una instancia para todas las otras funciones en esa clase de tipo, lo que podría no tener sentido.
La sobrecarga en Haskell solo está disponible usando clases de tipo. En este caso, (+)
pertenece a la clase de tipo Num
, por lo que debería proporcionar una instancia de Num
para su tipo.
Sin embargo, Num
también contiene otras funciones, y una instancia de buen comportamiento debe implementarlas todas de manera consistente, lo que en general no tendrá sentido a menos que su tipo represente algún tipo de número.
Entonces, a menos que ese sea el caso, recomendaría definir un nuevo operador en su lugar. Por ejemplo,
data Pair a b = Pair a b
deriving Show
infixl 6 |+| -- optional; set same precedence and associativity as +
Pair a b |+| Pair c d = Pair (a+c) (b+d)
Puede usarlo como cualquier otro operador:
> Pair 2 4 |+| Pair 1 2
Pair 3 6
Si solo desea el operador (+)
lugar de todos los operadores Num
, probablemente tenga una instancia de Monoid
, por ejemplo, la instancia de par de Monoid es como esta:
class (Monoid a, Monoid b) => Monoid (a, b) where
mempty = (mempty, mempty)
(a1, b1) `mappend` (a2, b2) = (a1 `mappend` a2, b1 `mappend` b2)
Puede hacer (++)
un alias de mappend
, luego puede escribir código como este:
(1,2) ++ (3,4) == (4,6)
("hel", "wor") ++ ("lo", "ld") == ("hello", "world")
Trataré de llegar a esta pregunta de manera muy directa, ya que está interesado en obtener un "sí o no" directo sobre la sobrecarga (+). La respuesta es sí, puedes sobrecargarla. Hay dos formas de sobrecargarlo directamente, sin ningún otro cambio, y una forma de sobrecargarlo "correctamente", que requiere crear una instancia de Num para su tipo de datos. La forma correcta se elabora en las otras respuestas, por lo que no voy a repasarlo.
Editar: Tenga en cuenta que no estoy recomendando el camino que se analiza a continuación, solo lo documenta. Debes implementar la clase de tipo Num y no todo lo que escribo aquí.
La primera (y la más "incorrecta") forma de sobrecargar (+) es simplemente ocultar la función Preludio. + Y definir su propia función llamada (+) que opera en su tipo de datos.
import Prelude hiding ((+)) -- hide the autoimport of +
import qualified Prelude as P -- allow us to refer to Prelude functions with a P prefix
data Pair a = Pair (a,a)
(+) :: Num a => Pair a -> Pair a -> Pair a -- redefinition of (+)
(Pair (a,b)) + (Pair (c,d)) = Pair ((P.+) a c,(P.+) b d ) -- using qualified (+) from Prelude
Puedes ver aquí, tenemos que pasar por algunas contorsiones para ocultar la definición regular de (+), pero aún necesitamos una forma de referirnos a ella, ya que es la única forma de hacer una adición rápida de la máquina (es una primitiva) operación).
La segunda forma (un poco menos equivocada) de hacerlo es definir su propia clase de tipos que solo incluye un nuevo operador que usted nombra (+). Aún tendrás que esconder el viejo (+) para que haskell no se confunda.
import Prelude hiding ((+))
import qualified Prelude as P
data Pair a = Pair (a,a)
class Addable a where
(+) :: a -> a -> a
instance Num a => Addable (Pair a) where
(Pair (a,b)) + (Pair (c,d)) = Pair ((P.+) a c,(P.+) b d )
Esto es un poco mejor que la primera opción porque le permite usar su nuevo (+) para muchos tipos de datos diferentes en su código.
Pero ninguno de estos se recomienda, porque como puede ver, es muy incómodo acceder al operador regular (+) que se define en la clase de tipo Num. Aunque haskell te permite redefinir (+), todos los preludios y las bibliotecas esperan la definición original (+). Por suerte para ti, (+) se define en una clase de tipo, por lo que puedes hacer que Pair sea una instancia de Num. Esta es probablemente la mejor opción, y es lo que los otros contestadores han recomendado.
El problema al que se está enfrentando es que posiblemente haya demasiadas funciones definidas en la clase de tipo Num (+ es una de ellas). Esto es solo un accidente histórico, y ahora el uso de Num está tan extendido que sería difícil cambiarlo ahora. En lugar de dividir esas funcionalidades en clases de tipos separadas para cada función (para que puedan ser anuladas por separado), todas se engloban juntas. Idealmente, el preludio tendría una clase de tipo Addable, y una clase de tipo Subtractable, etc., que le permiten definir una instancia para un operador a la vez sin tener que implementar todo lo que Num tiene en ella.
Sea como fuere, el hecho es que lucharás en una batalla cuesta arriba si quieres escribir un nuevo (+) solo para tu tipo de datos Pair. Demasiado del otro código Haskell depende de la clase de tipo Num y su definición actual.
Podrías buscar en el Preludio Numérico si estás buscando una reimplementación en el cielo azul del Preludio que intente evitar algunos de los errores del actual. Notarás que han vuelto a implementar el Preludio como una biblioteca, sin necesidad de hackear el compilador, aunque es una tarea enorme.