2011-09-17 11 views
6

¿Hay una clase para tipos que tienen un valor de unidad individual (no estoy seguro de la terminología correcta aquí), es decir, tipos con un valor predefinido?¿Hay una clase de "unidad"? ¿Sería útil?

class Unit a where 
    unit :: a 

instance Unit() where 
    unit =() 

instance Unit (Maybe a) where 
    unit = Nothing 

... para todos los monoides, MonadPlus, etc.

supongo que otro nombre para la clase podría ser Default. Esto habría sido útil dos veces recientemente ahora para mí.

ejemplo Probablemente poco convincente:

extract :: (Unit r)=> Reader r a -> a 
extract r = runReader r unit 

¿Existe esta? ¿Otros piensan que podría ser útil?

Respuesta

9

Sí, probablemente sería útil. De hecho, todas las versiones ligeramente incompatibles de esto serían útiles. Lo cual es un poco el problema.

No está claro lo que es una clase tan siquiera decir, lo que hace que sea difícil de usar en realidad, porque inevitablemente llegarás a tipos que ofrecen múltiples opciones de valor por defecto, y si no es inmediatamente claro cuál la instancia proporciona, prácticamente pierdes todos los beneficios de tener la clase en primer lugar.

Unos pocos ejemplos:

  • Para Monoid casos, se habían obviamente esperan que el elemento de identidad sea la predeterminada. Pero ahora ha vuelto al problema de tantos tipos que tienen dos o más instancias sensibles Monoid. ¿El valor predeterminado es Integer 0 o 1? Para Monoid, la biblioteca estándar usa contenedores newtype, pero estos son torpes y dificultan el trabajo con los tipos envueltos - con Monoid funciona bien porque tiene acceso a mconcat y tal, pero no puede hacer nada interesante con solo un valor predeterminado.

  • Para tipos de Functor -like con un valor "vacío", que da un valor predeterminado obvio. Esto es lo que están haciendo MonadPlus y Alternative ... y también se superpone con Monoid, y si la memoria me sirve, hay al menos un tipo en el que esas tres instancias no son idénticas. ¿Cuál escoges, cuando hay más de una opción? Considere listas: puede agregarlas ciegamente, dando un Monoid arbitrario, con la lista vacía como identidad; pero para listas de Monoids también puede zipWith mappend, dando un monoide levantado con repeat mempty como identidad. Muchos funtores tienen instancias de Monoid análogas, pero no siempre ambas; por lo tanto, cualquiera que elijas para las listas, ¡serás conceptualmente inconsistente con alguna otra Functor!

  • Para tipos de unidades como (), ¡no es difícil elegir un valor predeterminado! Pero, ¿y las enumeraciones? ¿Tiene sentido elegir el primer constructor? Algunas veces, pero no siempre. ¿Cómo sabrá la gente que usa la clase?

  • ¿Qué hay de Bounded? Si no se aplica ninguna de las opciones anteriores, puede usar minBound. Pero algunos de los tipos anteriores también pueden ser Bounded, por lo que confundirá si su valor predeterminado no es su valor mínimo.

Básicamente, sólo hay un solapamiento suficiente que parece tener sentido ... pero en realidad, usted tiene por lo menos tres clases de tipos diferentes en mente aquí, y tratar de unificarlos probablemente no es tan útil como parece al principio.


Si usted puede fijar las cosas un poco mejor y dar una interpretación clara y coherente semántico de un valor "default", sin acaba de reinventar Monoid u otra clase existente, de manera que la clase de tipo es fácil usar sin tener que detenerse y pensar qué es lo que se elige "predeterminado", ¡excelente! Pero no me ilusionaría hacer que funcione.

Dicho esto, el caso obviamente sensible que no está cubierto por ninguna clase de tipo estándar es singletons como (). La mayoría de las veces estos no son terriblemente útiles, ¡por razones obvias!, Que es probablemente la razón por la cual no existe tal clase. Un lugar donde tal clase es extremadamente útil, sin embargo, es cuando estás haciendo algo que involucra travesuras de nivel de tipo, porque tal tipo representa un solo valor en el tipo y nivel de término, entonces una clase para tales tipos le permite manipular los valores de nivel de tipo libremente, luego evoca el término que lo acompaña, por lo que puede pasarlo a alguna otra función que pueda, por ejemplo, seleccionar una instancia de clase de tipo basada en él. Por esa razón, no tengo a class along those lines in my perpetually-incomplete type-hackery library, por ejemplo .:

class TermProxy t where 
    term :: t 

-- This makes explicit the lexical pun of() having type(). 
instance TermProxy() where 
    term =() 

instance (TermProxy a, TermProxy b) => TermProxy (a, b) where 
    term = (term, term) 

dudo de tal clase es muy útil en cualquier otro contexto, sin embargo.

+0

Gracias por su respuesta generosa a una respuesta tan vaga. En realidad, no estoy interesado en la noción de "valores predeterminados" (¡probablemente no debería haberlo mencionado!), Sino más bien en estas cosas (como las que ocurren en la mano izquierda de una función (o abstracciones) ... Necesito pensar más acerca de cómo quiero usar esta clase. ¡Gracias! – jberryman

+1

@jberryman: Creo que tal vez lo que buscas es una clase de tipos para tipos con exactamente un constructor nullary, entonces? Que incluiría '()', 'Nothing',' [] ', etc., pero no booleanos o números ni nada de eso. Eso está al menos claramente definido y no es tan trivial como los singletons solos. –

+0

Sí, eso es correcto. a lo largo de las líneas de mi ejemplo 'Reader', estoy descubriendo que, para tipos que son receptores o funciones o Cofunctor-ish, puedo definir funciones interesantes para el caso especial'() '., p. 'liftCofunc :: (Cofunctor f) => f() -> f a'. Pensé que sería interesante ver si la función anterior podría definirse de manera más general. Gracias de nuevo. * EDIT *: Supongo que probablemente no sea una coincidencia que 'Cofunctor' tampoco sea una clase estándar;) – jberryman

6

Está buscando algún tipo de clase de tipo Default. Si bien la semántica de lo que debería ser un "incumplimiento" es discutible (y sugiero que acepte la respuesta de C.A. McCanns por sus valiosos comentarios), puede obtener una clase Default de un paquete comúnmente usado llamado data-default.

La clase es:

-- | A class for types with a default value. 
class Default a where 
    -- | The default value for this type. 
    def :: a 
+0

Muchas gracias. como le comenté a C.A. McCann, la noción de que esto sea un "default" fue una especie de reflexión y voy a tener que pensar bien acerca de lo que trato de hacer. – jberryman

+2

Sí, esa clase 'Default' es casi tan consistente como puedes esperar Todavía tiene algo de la confusión inherente que mencioné, por ejemplo, algunas instancias de 'Monad' son' return def', mientras que otras no. Por lo tanto, cualquier función con un contexto como '(Monad m, Default m a) =>' necesariamente se comportará de manera muy diferente para diferentes 'Monad's. –

+2

Encuentro 'data-default' particularmente útil para las estructuras de configuración y similares, es decir, cuando existe un valor predeterminado sensato dentro del contexto de una aplicación. Además, las instancias no se exportan. –

3

Si se quiere evitar una nueva clase, se puede definir la unidad en función de la clase de enumeración:

unit :: Enum a => a 
unit = toEnum 0 

O quizás mejor con la clase limitada como sigue:

unit :: Bounded a => a 
unit = minBound 

Ambos producen el resultado esperado para el tipo de unidad (y más probable para cualquier otra sola con tipo Structor):

*Main> unit ::() 
() 

Los inconvenientes en comparación con la clase de datos predeterminado (mencionado en otra respuesta) es que hay un menor número de casos, en particular ningún caso que devuelve [] para [a]. Además, el resultado no es el esperado de algún tipo, especialmente si usa minBound:

*Main> unit :: Int 
-2147483648 

*Main> unit :: Char 
'\NUL' 

*Main> unit :: Bool 
False 
Cuestiones relacionadas