Perché la lunghezza non è generica per impostazione predefinita?

6

Haskell spesso fornisce due versioni di una data funzione f, cioè:

f :: Int ...
genericF :: Integral i => i ...

Esistono molte funzioni di libreria standard con queste due versioni: length, take, drop, ecc.

Citazione della descrizione di genericLength :

The genericLength function is an overloaded version of length. In particular, instead of returning an Int, it returns any type which is an instance of Num. It is, however, less efficient than length.

La mia domanda è: da dove viene la perdita di efficienza? Il compilatore non può rilevare che stiamo usando genericLength come Int e quindi us length per prestazioni migliori? Perché non è length generico per impostazione predefinita?

    
posta Aegis 10.09.2013 - 19:42
fonte

2 risposte

4

È lo stesso motivo per cui abbiamo map e fmap . Messaggi di errore / usabilità per i neofiti.

Sarebbe potentissimo confondere per molti un nuovo programmatore per scrivere

myLength = subtract 1 . genericLength

x = myLength [1, 2, 3] :: Int
y = myLength [1, 2, 3] :: Integer

e ricevi un errore lamentando la limitazione del monomorfismo. Inoltre quale preferisci

Couldn't match expected type "Integer" with type "Bool"

o

No instance for Num Bool arising from usage ....

È semplicemente una questione di usabilità.

Inoltre, in molti casi finisci sempre con il default, ad esempio con la composizione delle funzioni

evenElems = even . subtract 1 . genericLength

vs

default ()
evenElems = even . genericLength -- Error ambiguous type variable.

Qui vogliamo solo che GHC "scelga" un tipo casuale di Num e davvero non ci interessa quale (è Integer IIRC).

Se tutto fosse completamente generico, le regole di default si sarebbero um..nomose. Dal momento che al momento ci sono solo 4 cose che sono automaticamente disponibili come predefinite e nessun modo per impostare qualcosa di granuloso (per funzione).

Per quanto riguarda l'efficienza, typeclass significa trascinare potenzialmente trascinando il dizionario di typeclass e il default a Integer che è molto più costoso di Int .

Ci sono preludi alternativi (penso che il preludio di classe sia non mantenuto, ma interessante) che cerchi di essere il più generico possibile. Usarli è semplice come

 {- LANGUAGE NoImplicitPrelude #-}
 import My.Super.Awesome.Prelude
    
risposta data 10.09.2013 - 22:57
fonte
4

Oltre all'usabilità, la ragione sembra essere davvero quella di evitare il degrado delle prestazioni. Nel recente Foldable in GHC7.10 FAQ di Prelude

length is left ungeneralized with regards to the numeric type primarily because genericLength has absolutely abysmal performance. One of the pragmatic guidelines we followed is that we can't make code slower.

Osservando l'implementazione:

lunghezza

{-# NOINLINE [1] length #-}
length                  :: [a] -> Int
length l                =  lenAcc l 0#

lenAcc :: [a] -> Int# -> Int
lenAcc []     a# = I# a#
lenAcc (_:xs) a# = lenAcc xs (a# +# 1#)

genericLength

genericLength           :: (Num i) => [a] -> i
{-# NOINLINE [1] genericLength #-}
genericLength []        =  0
genericLength (_:l)     =  1 + genericLength l

length ha un'implementazione coda-ricorsiva e probabilmente la funzione helper lenAcc può essere inline. Questo potrebbe spiegare la differenza, ma non sono sicuro che ci sia dell'altro.

    
risposta data 11.02.2015 - 00:07
fonte

Leggi altre domande sui tag