2012-05-16 53 views
15

Quiero una función +++ que añada dos vectores matemáticos.Haskell: entre una lista y una tupla

pude poner en práctica vectores como [x, y, z] y uso:

(+++) :: (Num a) => [a] -> [a] -> [a] 
(+++) = zipWith (+) 

Y así acomodar cualquier vector dimensional n (por lo que esto funcionaría para [x, y] también).

O podría implementar vectores como (x, y, z) y uso:

type Triple a = (a, a, a) 

merge :: (a -> b -> c) -> Triple a -> Triple b -> Triple c 
merge f (a, b, c) (x, y, z) = (f a x, f b y, f c z) 

(+++) :: (Num a) => Triple a -> Triple a -> Triple a 
(+++) = merge (+) 

Por supuesto, esto es un poco más complejo, pero cuando implemente todas las demás funciones vectoriales, que es irrelevante (50 líneas en lugar de 40).

El problema con el enfoque de lista es que puedo agregar un vector 2D con un vector 3D. En ese caso, zipWith simplemente cortaría el componente del vector 3D z. Si bien eso podría tener sentido (más probablemente debería expandir el vector 2D a [x, y, 0]), para otras funciones estoy pensando que podría ser problemático que cualquiera de las dos ocurra silenciosamente. El problema con el enfoque de tupla es que limita el vector a 3 componentes.

Intuitivamente, creo que tendría más sentido representar vectores como (x, y, z), ya que un vector matemático tiene un número fijo de componentes y realmente no tiene sentido contraer (anteponer) un componente a un vector.

Por otro lado, aunque es muy poco probable que necesite nada más que vectores 3D, no parece del todo correcto limitarlo a eso.

Supongo que lo que quiero son funciones que toman dos listas de la misma longitud, o mejor, funciones que operan en tuplas de tamaño arbitrario.

Cualquier sugerencias, en términos de practicidad, escalabilidad, elegancia, etc.?

+0

http: // stackoverflow.com/questions/7220953/does-haskell-have-variadic-functions-tuples –

+0

Sé que esta pregunta es un poco antigua, pero es posible que desee echar un vistazo al [vector-space] (http: //hackage.haskell paquete .org/paquete/vector-espacio). –

Respuesta

-1

Las respuestas de Landei y Leftaroundabout son buenas (gracias a los dos), y creo que debería haberme dado cuenta de que esto no sería tan simple como esperaba. Tratar de hacer cualquiera de las opciones que sugerí hace que el código sea complejo, lo que no sería un problema en sí mismo, excepto que parece que el código de usuario tampoco sería muy bonito.

Creo que he decidido ir con tuplas y seguir con vectores de 3 dimensiones, simplemente porque parece más semánticamente correcto que usar listas. Estoy terminando reimplementando map, zipWith, sum y otros para triples, sin embargo. Quiero seguir con la simplicidad. Siento que si tuviera un argumento convincente para pensar en los vectores como listas, entonces esa solución funcionaría mejor (siempre que me asegure de no mezclar las dimensiones) ... Cuando realmente uso los vectores, sin embargo, las funciones tomarán un vector 3d como argumento, no uno de dimensiones variables, y Num a => [a] no puede imponer eso.

+0

use' Data. Vector' del paquete 'vector' o' paquete ACVector' que tiene vectores 3D. Estas bibliotecas ya han definido funciones de ayuda, ahorrándole tiempo y energía. – vivian

+3

El código de una biblioteca puede ser complejo, pero puede ocultarlo bastante bien utilizando cosas como las definiciones de tipo y los métodos de conveniencia. – Landei

14

La manera más fácil es poner el operador +++ en una clase de tipo, y hacer que las diferentes tupla tamaños de instancias:

{-# LANGUAGE FlexibleInstances #-} -- needed to make tuples type class instances 

class Additive v where 
    (+++) :: v -> v -> v 

instance (Num a) => Additive (a,a) where 
    (x,y) +++ (ξ,υ) = (x+ξ, y+υ) 
instance (Num a) => Additive (a,a,a) where 
    (x,y,z) +++ (ξ,υ,ζ) = (x+ξ, y+υ, z+ζ) 
... 

esta manera, se pueden añadir tuplas de longitud variable, pero se garantizará en compilación - tiempo que ambos lados siempre tienen la misma longitud.


Generalizando esta opción para utilizar una función como su merge en la clase de tipo real es también posible: en este caso, es necesario especificar la instancia de clase como un constructor de tipos (como la lista mónada).

class Mergable q where 
    merge :: (a->b->c) -> q a -> q b -> q c 

instance Mergable Triple where 
    merge f (x,y,z) (ξ,υ,ζ) = (f x ξ, f y υ, f z ζ) 

y luego simplemente

(+++) :: (Mergable q, Num a) => q a -> q b -> q c 
+++ = merge (+) 

Por desgracia, esto no funciona del todo, porque sinónimos de tipos no pueden ser evaluados parcialmente.Es necesario hacer un Triple newtype lugar, como

newtype Triple a = Triple(a,a,a) 

y luego

instance Mergable Triple where 
    merge f (Triple(x,y,z)) (Triple((ξ,υ,ζ)) = Triple(f x ξ, f y υ, f z ζ) 

que por supuesto no es tan agradable a la vista.

+3

@VladtheImpala: quizás prefiera [japonés] (http://codegolf.stackexchange.com/a/4824/2183)? - En serio, ¿qué hay de malo en llamar a las variables locales nombres griegos? No obliga a nadie a escribir en su propio código. Si conoce el alfabeto griego, tiene sentido asociar, p. z con zeta, y si no lo hace, hace poca diferencia en comparación con letras latinas arbitrarias. – leftaroundabout

21

Puede usar la programación de nivel de tipo. Primero tenemos que hacer que cada número natural sea un tipo separado. Tras la definición de los números naturales de Peano, Z es 0, y es S xx + 1

data Z = Z 
data S a = S a 

class Nat a 
instance Nat Z 
instance (Nat a) => Nat (S a) 

Ahora podemos usar un tipo Vec simplemente envolver una lista, pero para realizar un seguimiento de su tamaño mediante el uso de Nat. Para ello, se utiliza el smart constructorsnil y <:> (por lo que no debería exportar el constructor de datos Vec de su módulo)

data Vec a = Vec a [Int] 

nil = Vec Z [] 

infixr 5 <:> 
x <:> (Vec n xs) = Vec (S n) (x:xs) 

Ahora podemos definir una función add, lo que requiere que dos vectores tienen la misma Nat:

add :: Nat a => Vec a -> Vec a -> Vec a 
add (Vec n xs) (Vec _ ys) = Vec n (zipWith (+) xs ys) 

Ahora usted tiene un tipo de vectores con la información de longitud:

toList (Vec _ xs) = xs 
main = print $ toList $ add (3 <:> 4 <:> 2 <:> nil) (10 <:> 12 <:> 0 <:> nil) 

Por supuesto que tener vectores con diferente longitud aquí causará un error de compilación.

Esta es la versión fácil de entender, hay soluciones más cortas, más eficientes y/o más convenientes.

+0

Me pregunto por las soluciones "más eficientes y/o más convenientes", ¿alguien se preocupa por dar algunos consejos? – sinan

+0

Creo que puede obtener efectos similares con 'HList's: http://hackage.haskell.org/packages/archive/HList/0.2.3/doc/html/Data-HList-HListPrelude.html – Landei

1

Como OP quería un enfoque más liviano, usaría los tipos asociados.

class VecMath a b where 
    type Res a b :: * 
    (+++) :: a -> b -> Res a b 

instance Num a => VecMath (a,a,a) (a,a,a) where 
    type Res (a,a,a) (a,a,a) = (a,a,a) 
    (x1,y1,z1) +++ (x2,y2,z2) = (x1+x2, y1+y2, z1+z2) 

instance Num a => VecMath (a,a) (a,a,a) where 
    type Res (a,a) (a,a,a) = (a,a,a) 
    (x1,y1) +++ (x2,y2,z) = (x1+x2, y1+y2, z) 

instance Num a => VecMath (a,a,a) (a,a) where 
    type Res (a,a) (a,a,a) = (a,a,a) 
    -- (+++) analog 
instance Num a => VecMath (a,a) (a,a) where 
    type Res (a,a) (a,a) = (a,a) 
    -- ... 

Res es una función de tipo, aquí esencialmente lo que resulta en el tipo 'más grande' de sus argumentos. La ventaja es que todavía puede trabajar con tuplas antiguas simples, como si VecMath no existiera. El lado oscuro es la explosión exponencial de instancias que tiene que escribir, si considera agregar nuevos tipos al dominio de Res. Para obtener más información, consulte this.

Cuestiones relacionadas