2014-12-14 13 views
21

Pytałem o tym wcześniej, ale wydaje się, że sformułowałem to pytanie zbyt wąsko. Zobaczmy więc, czy mogę wyjaśnić, po co właściwie jestem.showsPrec i operator precedensy

Załóżmy, że mam typ, który obsługuje kilku operatorów binarnych, z których każdy ma różny priorytet i jest powiązany. Jak napisać instancję Show, która poprawnie wykonuje podekspozycje?

Wiem, że jestem tutaj gęsty, ale źle to rozumiem za każdym razem Próbuję to zrobić. Musi istnieć jakaś mechaniczna procedura, którą możesz wykonać, aby to poprawnie działało, ale nie mogę go znaleźć. Czy ktoś może przejść przez przykład?

wiem, że to ostatecznie sprowadza się do zawijania wszystko w showParen i pokazując sub-wyrażeń za pomocą showsPrec z prawej magicznej liczby, i mogę to zrobić prawie praca, ale to nigdy nie działa prawo w wszystkie okoliczności .


Edit: Rozważmy następujący kod

data Expr = 
    Const Int | 
    Expr :+: Expr | 
    Expr :-: Expr | 
    Expr :*: Expr | 
    Expr :/: Expr 

infixl 6 :+: 
infixl 6 :-: 
infixl 7 :*: 
infixl 7 :/: 

instance Show Expr where 
    showsPrec p e0 = 
    case e0 of 
    Const n -> shows n 
    x :+: y -> showParen (p > 6) $ (showsPrec 6 x) . (" :+: " ++) . (showsPrec 6 y) 
    x :-: y -> showParen (p > 6) $ (showsPrec 6 x) . (" :-: " ++) . (showsPrec 6 y) 
    x :*: y -> showParen (p > 7) $ (showsPrec 7 x) . (" :*: " ++) . (showsPrec 7 y) 
    x :/: y -> showParen (p > 7) $ (showsPrec 7 x) . (" :/: " ++) . (showsPrec 7 y) 

This prawie działa poprawnie:

*Main> Const 1 :+: Const 2 :*: Const 3 :+: Const 4 
1 :+: 2 :*: 3 :+: 4 
*Main> (Const 1 :+: Const 2) :*: (Const 3 :+: Const 4) 
(1 :+: 2) :*: (3 :+: 4) 

ale nie całkiem:

*Main> Const 1 :+: Const 2 :-: Const 3 :-: Const 4 
1 :+: 2 :-: 3 :-: 4 
*Main> Const 1 :+: Const 2 :-: (Const 3 :-: Const 4) 
1 :+: 2 :-: 3 :-: 4 

Wygląda na to, że pierwszeństwo ma , ale skojarzenie jest zrywane.

+1

Google sugeruje następujące dwa pytania: [Pretty Printing AST z minimalnymi nawiasami] (http: // stackoverflow.com/q/13708837/791604) i [asocjacja i pierwszeństwo wyrażeń podczas generowania kodu C/C++?] (http://stackoverflow.com/q/4306680/791604). Mogą ci dać pewne pomysły. Nie jestem pewien, czy kwalifikuje się to jako duplikat jednego z nich, czy nie, ale na razie skłaniam się ku "nie dup", ponieważ to pytanie jest po części o tym, jak sprawić, aby ogólne techniki pasowały do ​​ekosystemu Haskell. –

Odpowiedz

13

Ponieważ showsPrec nie ma żadnego sposobu na uzyskanie asocjatywności kontekstu, nie sądzę, że jest to możliwe, aby to naprawić, uzyskać dokładnie minimalny nawias Haskella. W celu zapewnienia poprawności bez dodawania kolejnych nadmiarowych nawiasów niż to jest konieczne, należy >= w stanie showParen:

showsPrec p e0 = 
    case e0 of 
    Const n -> shows n 
    x :+: y -> showParen (p >= 6) $ (showsPrec 6 x) . (" :+: " ++) . (showsPrec 6 y) 
    x :-: y -> showParen (p >= 6) $ (showsPrec 6 x) . (" :-: " ++) . (showsPrec 6 y) 
    x :*: y -> showParen (p >= 7) $ (showsPrec 7 x) . (" :*: " ++) . (showsPrec 7 y) 
    x :/: y -> showParen (p >= 7) $ (showsPrec 7 x) . (" :/: " ++) . (showsPrec 7 y) 

Skutkuje to uzyskaniem

* Główny> konstrukcyjna 1: +: konstrukcyjna 2: *: konstrukcyjna 3: +: Const 4
(1: +: 2: *: 3): +: 4
* Główna> (Const 1: +: Const 2): *: (Const 3: +: Const 4)
(1: +: 2): *: (3: +: 4)
* Główna> Const 1: +: Const 2: -: Const 3: -: Const 4
((1: +: 2): -: 3): -: 4
* Główny> Const 1: +: Const 2: -: (Const 3: -: Const 4)
(1: + 2): -: (3: -: 4)

które nie wygląda tak dobre, jak mógł, ale nie jest tak źle i na pewno nie źle jak wersja showParen (p > n). Zasadniczo daje to minimalną parentetyzację, gdybyśmy mieli tylko infix, infixl lub infixr.

Jeśli chcesz, aby pojawiły się tylko te pareny, które są naprawdę konieczne, musisz propagować więcej informacji niż tylko Int dla utrwalenia kontekstu. Zaimplementowałem tego rodzaju rzeczy in my symbolic-math extension idea for HaTeX; w gruncie rzeczy odzwierciedla on adnotacje Haskella po infixl itd. Na przykład,

 exaDisp $ 5 - (4 - 3) + 2 + 1 

zostaje wygenerowana jak

Minimal-parenthesization in HaTeXMath

0

Jak o tym:

prec :: Expr -> Int 
prec (Const _) = 10 
prec (_ :*: _) = 7 
prec (_ :/: _) = 7 
prec (_ :+: _) = 6 
prec (_ :-: _) = 6 

instance Show Expr where 
    showsPrec p e0 = 
    case e0 of 
    Const n -> shows n 
    x :+: y -> showbin 6 " + " x y 
    x :-: y -> showbin 6 " - " x y 
    x :*: y -> showbin 7 " * " x y 
    x :/: y -> showbin 7 "/" x y 
    where showbin pr s x y = 
      showParen (p > pr) $ 
      showsPrec pr x . (s ++) . 
      showParen (prec y == pr) (showsPrec pr y) 

powodując

*Main> (Const 1 :+: Const 2) :*: (Const 3 :+: Const 4) 
(1 + 2) * (3 + 4) 
*Main> Const 1 :+: Const 2 :-: Const 3 :-: Const 4 
1 + 2 - 3 - 4 
*Main> Const 1 :+: Const 2 :-: (Const 3 :-: Const 4) 
1 + 2 - (3 - 4) 
*Main> Const 1 :+: Const 2 :-: (Const 3 :-: Const 4 :-: Const 5) 
1 + 2 - (3 - 4 - 5) 
*Main> Const 1 :+: Const 2 :-: (Const 3 :-: (Const 4 :-: Const 5)) 
1 + 2 - (3 - (4 - 5)) 
*Main> Const 1 :+: Const 2 :-: (Const 3 :*: (Const 4 :/: Const 5)) 
1 + 2 - 3 * (4/5) 
*Main> (Const 1 :*: (Const 2 :-: (Const 3 :*: (Const 4 :/: Const 5)))) 
1 * (2 - 3 * (4/5)) 
6

Poniżej Show instancja będzie drukować typ Expr z minimalnymi nawiasach:

data Expr = 
    Const Int | 
    Expr :+: Expr | 
    Expr :-: Expr | 
    Expr :*: Expr | 
    Expr :/: Expr 

infixl 6 :+: 
infixl 6 :-: 
infixl 7 :*: 
infixl 7 :/: 

instance Show Expr where 
    showsPrec p e0 = 
    case e0 of 
    Const n -> showParen (p > 10) $ showString "Const " . showsPrec 11 n 
    x :+: y -> showParen (p > 6) $ showsPrec 6 x . showString " :+: " . showsPrec 7 y 
    x :-: y -> showParen (p > 6) $ showsPrec 6 x . showString " :-: " . showsPrec 7 y 
    x :*: y -> showParen (p > 7) $ showsPrec 7 x . showString " :*: " . showsPrec 8 y 
    x :/: y -> showParen (p > 7) $ showsPrec 7 x . showString " :/: " . showsPrec 8 y 

Wynika to z:

*Main> Const 1 :+: Const 2 :*: Const 3 :+: Const 4 
Const 1 :+: Const 2 :*: Const 3 :+: Const 4 
*Main> (Const 1 :+: Const 2) :*: (Const 3 :+: Const 4) 
(Const 1 :+: Const 2) :*: (Const 3 :+: Const 4) 
*Main> Const 1 :+: Const 2 :-: Const 3 :-: Const 4 
Const 1 :+: Const 2 :-: Const 3 :-: Const 4 
*Main> Const 1 :+: Const 2 :-: (Const 3 :-: Const 4) 
Const 1 :+: Const 2 :-: (Const 3 :-: Const 4) 

Ogólną zasadą jest

  • infix n: użyj showParen (p > n), showsPrec (n+1) po lewej stronie, a showsPrec (n+1) po prawej
  • infixl n: używać showParen (p > n), showsPrec n po lewej i showsPrec (n+1) po prawej
  • infixr n: użyj showParen (p > n), showsPrec (n+1) na lewo i showsPrec n po prawej
  • non-wrostkiem: użyj showParen (p > 10) i showsPrec 11 na argumentach

Zgodnie z tą regułą zawsze otrzyma poprawną składnię z minimalnymi nawiasami, z wyjątkiem jednego przypadku na rogu: Może generować niejednoznaczne dane wyjściowe, jeśli masz konstruktory infixl i infixr z tym samym poziomem priorytetu. Dopóki tego nie zrobisz, powinno być dobrze.


Skąd wiem, jakie argumenty do korzystania z showParen? Ma to pasować do tego, co Haskell robi dla instancji Show. Możemy przetestować te tak:

data T = P :# P | T P 
    deriving Show 

infix 6 :# 

data P = P 

instance Show P where 
    showsPrec p P = shows p 

Możemy zobaczyć, że z infix 6 :#, instancja Show T wzywa showsPrec 7 na argumenty :#, a także pokazuje nawiasów tylko w precedensami> 6:

*Main> showsPrec 6 (P :# P) "" 
"7 :# 7" 
*Main> showsPrec 7 (P :# P) "" 
"(7 :# 7)" 

Natomiast dla zwykłego konstruktora T wygenerowana instancja wywołuje showsPrec 11 dla argumentu i pokazuje wartości początkowe o wartościach domyślnych> 10:

*Main> showsPrec 10 (T P) "" 
"T 11" 
*Main> showsPrec 11 (T P) "" 
"(T 11)"