2014-05-10 19 views
5

C'è un modo facile e veloce per scoprire la precedenza e l'associatività di una funzione in GHCI?Come scoprire la precedenza e l'associatività di una funzione in GHCI?

Ho trovato che un metodo semplice è quello di bruteforce che combina un operatore con un altro fino a ottenere un errore di analisi della precedenza (PPE). Ad esempio, per scoprire la precedenza/associatività di !!, possiamo definire 10 diversi operatori di infix n per 0 ≤ n < 10:

>>> let infix 0 %; (%) = ($) 
>>> undefined % undefined !! undefined 
*** Exception: Prelude.undefined 
>>> let infix 1 %; (%) = ($) 
>>> undefined % undefined !! undefined 
*** Exception: Prelude.undefined 
[...] 
>>> let infix 8 %; (%) = ($) 
>>> undefined % undefined !! undefined 
*** Exception: Prelude.undefined 
>>> let infix 9 %; (%) = ($) 
>>> undefined % undefined !! undefined 

<interactive>:21:1: 
    Precedence parsing error 
     cannot mix `%' [infix 9] and `!!' [infixl 9] in the same infix expression 

L'errore ti dice quello che i predences e fissità sono. Poiché le 10 funzioni definite sono non associative, non c'è possibilità di evitare un DPI quando i livelli di precedenza sono uguali. L'approccio ingenuo sarebbe stato quello di provare 10 funzioni di ogni associatività, dando un WCET di 20 (perché se si inizia sulla stessa associatività del bersaglio, il 10 con quell'associatività passerebbe, ma poi l'errore si verificherà una volta che si arriva a la precedenza della prossima associatività kth, dove k è la precedenza della funzione di destinazione).

Ma c'è un modo più veloce? I pensare in si può fare una bisezione per ottenere in media i passi di log (10). Ad esempio, se l'obiettivo era !!! definito come !!, ma con infixl 6, è possibile creare un'espressione (:[]) % "AB" !!! 1, dove % è una delle stesse 10 funzioni fittizie. Se la precedenza di % è troppo bassa per causare un DPI, l'espressione produrrà "B" (poiché l'espressione verrà analizzata a (:[]) % ("AB" !!! 1)), mentre se la precedenza è troppo alta, produrrà (poiché l'espressione verrà analizzata a ((:[]) % "AB") !!! 1). Esempio:

>>> let infixl 6 !!!; (!!!) = (!!) 
>>> let infix 5 %; (%) = ($) 
>>> (:[]) % "AB" !!! 1 
"B" 
>>> --too low 
>>> let infix 7 %; (%) = ($) 
>>> (:[]) % "AB" !!! 1 
"*** Exception: Prelude.(!!): index too large 

>>> --too high 
>>> let infix 6 %; (%) = ($) 
>>> (:[]) % "AB" !!! 1 

<interactive>:10:1: 
    Precedence parsing error 
     cannot mix `%' [infix 6] and `!!!' [infixl 6] in the same infix expression 
>>> --got it 

Tuttavia, non sono sicuro di questo approccio.

  • È necessario mantenere lo stato mentale di cui il numero di indovinare il prossimo, e di fare l'etc divisione nella tua testa (per carta/calcolatore sarebbe probabilmente più lento di solo facendo delle 10 fasi del metodo semplice)
  • È completamente generale? Puoi sempre costruire un'espressione di test come questa?
  • io non sono sicuro di poter sempre venire con un'espressione del genere più veloce di quello che posso solo fare il metodo semplice. In questo caso è stato davvero veloce, e io in realtà si avvicinò con il metodo di bisezione prima mi si avvicinò con il metodo semplice
  • Se la preoccupazione precedente tiene valido, se esiste un modo del tutto generale, per generare automaticamente espressioni di questo genere?

È qualcuno a conoscenza di un metodo alternativo? Posso immaginare che ci possa essere un modo per farlo in un solo passaggio. Ma dopo un'ora, il meglio che ho potuto inventare è questo metodo di bisezione.

risposta

15

Sì. È possibile utilizzare il comando :info.

Prelude> :info (+) 
class Num a where 
    (+) :: a -> a -> a 
    ... 
     -- Defined in `GHC.Num' 
infixl 6 + 

Prelude> :info (.) 
(.) :: (b -> c) -> (a -> b) -> a -> c -- Defined in `GHC.Base' 
infixr 9 . 

Prelude> :info ($) 
($) :: (a -> b) -> a -> b  -- Defined in `GHC.Base' 
infixr 0 $ 

Se non dire, è il default di infixl 9.

+2

Puoi anche digitare solo, ad esempio, ': i +'. –

15

risposta s' @kqr è ovviamente meglio da GHCi, ma mi limiterò a notare che v'è un modo rapido simile a quello che si è tentato, che a volte io uso in canali IRC con lambdabot (che non supporta :info).

>>> (0$0 !!) 

causa del requisito sezione che (expr `op`) può essere utilizzato solo se expr `op` var analizza identico a (expr) `op` var, nonché $ essere proprio associativo di fissità disponibilità, che espressione sarà mai parse indipendentemente fissità di !!, e ti darà la fissità di !! nel messaggio di errore.

+1

Hmm. Sono tentato di accettare questa come risposta, poiché funziona per tutte le precedenze (anche quelle predefinite), al contrario di ': info'. È ancora una cosa da memorizzare, ma lo è anche ': info'. Inoltre ': info' ti fa memorizzare la precedenza/associatività di default. – Dog

+0

@Dog: Sì, i WCET sono entrambi 1, ma è necessario memorizzare il doppio delle informazioni da usare ': info:'. Tuttavia, direi che * è * vale la pena di memorizzare invece ': info' e che la fissità predefinita è' infixl 9'. Quest'ultimo fatto è necessario se stai scrivendo la tua implementazione Haskell, quindi potresti anche solo memorizzarla. Ciò comporta un risparmio ammortizzato una volta che si ottiene l'implementazione di Haskell. Inoltre, ': info' è utile per altri scopi, come visualizzare le istanze di una classe o trovare dove è definito un valore. –

+0

Giusto, anche quando stai leggendo del codice, devi conoscere l'associatività/precedenza per sapere cosa fa, quindi questo è un altro caso per usare ': info'. – Dog