haskell - functors - Haciendo(a, a) un Functor
functor haskell (3)
Como han dicho otros, no hay forma de hacer esto sin recurrir a nuevos tipos o declaraciones de datos. Sin embargo, ¿has mirado Control.Arrow
? Muchas de esas funciones son muy útiles con tuplas, por ejemplo:
vmap :: (a -> b) -> (a,a) -> (b,b)
vmap f = f *** f
¿Cómo puedo hacer (a, a)
un Functor
sin recurrir a un newtype
?
Básicamente quiero que funcione así:
instance Functor (a, a) where
fmap f (x, y) = (f x, f y)
Pero, por supuesto, esa no es una forma legal de expresarlo:
Kind mis-match
The first argument of `Functor'' should have kind `* -> *'',
but `(a, a)'' has kind `*''
In the instance declaration for `Functor (a, a)''
Lo que realmente quiero es una función de nivel de tipo como esta: /a -> (a, a)
(sintaxis no válida). Entonces, ¿un alias de tipo, tal vez?
type V2 a = (a, a)
instance Functor V2 where
fmap f (x, y) = (f x, f y)
Pensaría que esto funcionaría, pero no funciona. Primero recibo esta queja:
Illegal instance declaration for `Functor V2''
(All instance types must be of the form (T t1 ... tn)
where T is not a synonym.
Use -XTypeSynonymInstances if you want to disable this.)
In the instance declaration for `Functor V2''
Si sigo los consejos y agrego la extensión TypeSynonymInstances
, recibo un nuevo error:
Type synonym `V2'' should have 1 argument, but has been given 0
In the instance declaration for `Functor V2''
Bueno, duh, ese es el punto! V2
tiene el tipo * -> *
que es lo que se requiere de una instancia de Functor
. Bueno, ok, puedo usar un newtype
como este:
newtype V2 a = V2 (a, a)
instance Functor V2 where
fmap f (V2 (x, y)) = V2 (f x, f y)
Pero ahora tengo que esparcir generosamente V2
largo de mi código, en lugar de poder lidiar con tuplas simples, lo que derrota el punto de convertirlo en un Functor
; en ese punto yo también podría hacer mi propia función vmap :: (a -> b) -> (a, a) -> (b, b)
.
Entonces, ¿hay alguna manera de hacerlo bien, es decir, sin un newtype
?
Con singletons puede definir una clase de tipo de Functor
para símbolos desfuncionalizados ( Type ~> Type
lugar de Type -> Type
)
{-# Language ExplicitNamespaces, TypeApplications, TypeOperators, KindSignatures, ScopedTypeVariables, DataKinds, TypeInType, TypeFamilies, AllowAmbiguousTypes, InstanceSigs #-}
import Data.Kind (Type)
import Data.Singletons (type (~>), Apply)
class Functor'' (f :: Type ~> Type) where
fmap'' :: (a -> a'') -> (Apply f a -> Apply f a'')
data Dup :: Type ~> Type
type instance Dup `Apply` a = (a, a)
instance Functor'' Dup where
fmap'' :: (a -> a'') -> ((a, a) -> (a'', a''))
fmap'' f (a1, a2) = (f a1, f a2)
Esto te da una instancia de Prelude.Functor
automáticamente
newtype f $ a = App (Apply f a)
instance Functor'' f => Functor (($) f) where
fmap :: (a -> a'') -> (f $ a -> f $ a'')
fmap f (App fa) = App (fmap'' @f f fa)
Puedes declarar
instance Functor ((,) a) where
...
Sin embargo, eso no restringe el primer elemento de su par, y fmap
solo actuará en el segundo elemento.
El problema es que una tupla no impone una relación entre los tipos de los dos elementos.
Si no quieres un decorador newtype
puedes hacer tu propio tipo fresco:
data Pair a = P a a
instance Functor Pair where
...
que será más fácil trabajar con un newtype
alrededor de una tupla.