2

Given the following code:

data Exprs = Const Double
        | Var String      --in math we often use char
        | Sqrt Exprs      --we can take sqrt of anything
        | IntPow Exprs Int--the easy case of exponents
        | Exp Exprs       --e^expr
        | Ln Exprs        --logarithms
        | Mult Exprs Exprs--multiplication
        | Add Exprs Exprs
        | Neg Exprs
deriving (Show, Eq, Ord)

x = Var "x"
y = Var "y"
z = Var "z"


-- to multiply x and y we type in "Mult x y"

example = Add (Const 7) ( Mult (Const 4) (Add (Sqrt x) (Exp y)))

How would I make a function that displays 7 + 4 * (sqrt(x)) + e^y from example?

  • 1
    Define the `Show` instance manually by implementing [`showPrec`](http://hackage.haskell.org/package/base-4.7.0.1/docs/Prelude.html#v:showsPrec). Or just write your own `prettyExpr` that is *de facto* an implementation of `showPrec` even if it isn't part of the instance. The key point is that you have to pass around some information that tells you the "context precedence" and you have to know the "precedence level" of the operators. Based on this information you can decide whether to add parenthesis or not around sub-expressions. – Bakuriu Oct 02 '14 at 21:01
  • 1
    Check out [my answer to this question](http://stackoverflow.com/a/26131468/839246). It has an example of showing an expression language like this with appropriate parens and whatnot, but you'll have to implement function calls yourself (check out the docs in `Text.Show` for some extra tips) – bheklilr Oct 02 '14 at 21:11
  • Also, your example, you have the last term as `Mult (Const 4) (Add (Sqrt x) (Exp y))`, but the pretty printed output you want says `4 * (sqrt(x)) + e^y`, which would be the value `Add (Mult (Const 4) (Sqrt x)) (Exp y)`, a significantly different expression. Did you mean for those parentheses around `sqrt(x)` in your example output to include the `e^y` as well? – bheklilr Oct 02 '14 at 21:31

3 Answers3

2

What you want to implement here is the showsPrec function in the Show typeclass. This function takes an extra argument that indicates the precedence of the operation, allowing you to more easily achieve sane parentheses, and it also is more efficient since it uses what amounts to a diff list for building the string (more efficient concatenation). The function show defaults to \x -> showsPrec 0 x "", so when you call show it will work properly. An incomplete example for your case would be

data Exprs
    = Const Double
    | Var String      --in math we often use char
    | Sqrt Exprs      --we can take sqrt of anything
    | IntPow Exprs Int--the easy case of exponents
    | Exp Exprs       --e^expr
    | Ln Exprs        --logarithms
    | Mult Exprs Exprs--multiplication
    | Add Exprs Exprs
    | Neg Exprs
    deriving (Eq, Ord)

instance Show Exprs where
    showsPrec n (Const x)  = showParen (n > 10) $ showsPrec 11 x
    showsPrec n (Var var)  = showParen (n > 10) $ showString var
    showsPrec n (Add l r)  = showParen (n >  6) $ showsPrec 7 l . showString "+" . showsPrec 7 r
    showsPrec n (Mult l r) = showParen (n >  7) $ showsPrec 8 l . showString "*" . showsPrec 8 r
    showsPrec n (Sqrt e)   = showParen (n > 10) $ showString "sqrt(" . shows e . showString ")"

I'll leave it to you to implement the other constructors (and test it heavily to ensure there are no mistakes, I do not guarantee that this is 100% correct), but you should have a pretty good start here. You may want to experiment with :i (*), :i (+), and :i (**) to figure out where the precedences I've used have come from.

bheklilr
  • 53,530
  • 6
  • 107
  • 163
2

As I posted in a comment to @ThreeFx's answer, I think it's bad practice to use the Show typeclass for pretty-printing or other general string munging. The documentation for Show notes that, for derived instances, "The result of show is a syntactically correct Haskell expression" made of constructors, constants, etc. That's not a rule per se, but it's a very helpful invariant. When you evaluate an expression in ghci, for instance, you expect to get a result that you can then copy-paste and reuse in code. Using Show to perform the operation you want breaks that expectation.

Rather, I think you should expose a function--not show--that you can properly document, etc., and that doesn't violate the implicit contract on Show instances. @Bakuriu suggested as much in a comment.

The implementation of that function can be almost identical to the solutions @ThreeFx and @bheklilr proposed... just without the instance part. Here's my rendition of @bheklilr's version:

data Exprs
    = Const Double
    | Var String      --in math we often use char
    | Sqrt Exprs      --we can take sqrt of anything
    | IntPow Exprs Int--the easy case of exponents
    | Exp Exprs       --e^expr
    | Ln Exprs        --logarithms
    | Mult Exprs Exprs--multiplication
    | Add Exprs Exprs
    | Neg Exprs
    deriving (Eq, Ord, Show, Read)

prettyPrint :: Exprs -> String
prettyPrint e = go 0 e ""
  where
    go n (Const x)  = showParen (n > 10) $ showsPrec 11 x
    go n (Var var)  = showParen (n > 10) $ showString var
    go n (Add l r)  = showParen (n >  6) $ go 7 l . showString "+" . go 7 r
    go n (Mult l r) = showParen (n >  7) $ go 8 l . showString "*" . go 8 r
    go n (Sqrt e)   = showParen (n > 10) $ showString "sqrt(" . go n e . showString ")"

Note that all I've done is mechanically rewrite showsPrec as go and wrap it in a convenience function.

Now Read and Show work dandy, and we can get nice pretty-printing:

*SO26169469> Add (Const 7) ( Mult (Const 4) (Add (Sqrt (Var "x")) (Const 3)))
Add (Const 7.0) (Mult (Const 4.0) (Add (Sqrt (Var "x")) (Const 3.0)))
*SO26169469> show it
"Add (Const 7.0) (Mult (Const 4.0) (Add (Sqrt (Var \"x\")) (Const 3.0)))"
*SO26169469> read it :: Exprs
Add (Const 7.0) (Mult (Const 4.0) (Add (Sqrt (Var "x")) (Const 3.0)))
*SO26169469> prettyPrint it
"7.0+4.0*(sqrt(x)+3.0)"

P.S. I don't claim to be any authority; I'm sure many people would support using Show as the other contributors suggest.

Christian Conkle
  • 5,932
  • 1
  • 28
  • 52
0

Careful: Bad practice ahead!

As Christian pointed out in his comment, show should yield a syntactically valid Haskell construct, which is why overriding show is not considered good practice. You should hava a look at bheklilr's answer or Bakuriu's comments on the question.


Nevertheless, one possible way is to implement the Show instance manually for every constructor:

instance Show Exprs where
  show (Const d) = show d
  show (Var   v) = v
  show (Sqrt ex) = "(sqrt(" ++ show ex ++ "))"
  ..
  show (Add e1 e2) = show e1 ++ " + " ++ show e2
  ..

Sample output:

*Main> Sqrt (Var "x")
(sqrt(x))
*Main> Add (Sqrt (Const 4)) (Var "x")
(sqrt(4.0)) + x
ThreeFx
  • 7,250
  • 1
  • 27
  • 51
  • 1
    I object to solving this problem with `Show`. It's not *wrong*, but it sets a bad habit, one that unfortunately some library authors also display. The documentation for `Show` notes that, for derived instances, "The result of `show` is a syntactically correct Haskell expression... [containing] only constructor names..." That's not a rule per se, but it's a very helpful invariant. When you evaluate an expression in `ghci`, for instance, you expect to get a result that you can then copy-paste and reuse in code. Your solution breaks that expectation. – Christian Conkle Oct 02 '14 at 21:20
  • @ChristianConkle I edited my answer to include your comment, although I will remove it if deleting it is more appropriate. Also thanks for pointing it out! – ThreeFx Oct 02 '14 at 21:29
  • @ChristianConkle, are you talking about `Data.Text`, by chance? – dfeuer Oct 02 '14 at 22:24
  • @dfeuer: No; with `Data.Text` it works out OK as long as you have `OverloadedStrings`. There's no need to pedantically expose the literal structure of the data. The first library that pops into mind is actually `vinyl`--a library I quite like--which long ago used to export a `Show` instance that "looked nice" but was syntactic nonsense. – Christian Conkle Oct 02 '14 at 22:52