Question

I often use list comprehension for optional values:

[Parent parent, Destination [DestPage currPage]] ++ [OpenChildren | parent == Bookmark 0]

But i do not know how to do a choice instead of optional value.

A lot of my code looks like this:

let lblTabX         = if isAtBottom then 5 else 3
    lblTabY         = if isAtBottom then 3 else 60
    lblTabPosition  = Position left (if isAtBottom then bottom else top)
    lblTabWidth     = if isAtBottom then lblPageX - 60 else 20
    lblTabHeight    = if isAtBottom then 20 else pageHeight - 80
    lblTabMargin    = if isAtBottom then Margin 0 3 else Margin 3 0

As you see a lot of ifs :)

So i was playing with some operators and came up with this syntax:

iif c l r = if c then l else r

infixl 8 <-/
(<-/) l c = iif c l

infixl 8 /->
(/->) = ($)

And i like how the previous example now looks:

let lblTabX         = 5 <-/ isAtBottom /-> 3
    lblTabY         = 3 <-/ isAtBottom /-> 60
    lblTabPosition  = Position left (bottom <-/ isAtBottom /-> top)
    lblTabWidth     = (lblPageX - 60) <-/ isAtBottom /-> 20
    lblTabHeight    = 20 <-/ isAtBottom /-> (pageHeight - 80)
    lblTabMargin    = Margin 0 3 <-/ isAtBottom /-> Margin 3 0

This is a toy example of course. I have no intention of using it. But i was just curious, is there a syntax to express the choice besides the if operator? Maybe with list comprehensions?

Was it helpful?

Solution

ifs in Haskell aren't terribly pretty, but that's not really the reason they're used so seldom. It's more because there's usually a more elegant syntactic alternative! In your example, I would consider

let (                lblTabX, lblTabY, lblTabPosition, lblTabWidth, lblTabHeight,    lblTabMargin )
     | isAtBottom =( 5,       3,       bottom,         lblPageX-60, 20,              Margin 0 3   )
     | otherwise  =( 3,       60,      top,            20,          pageHeight - 80, Margin 3 0   )

Alternatively, you might define an already partially evaluated operator locally:

let bottomCase/|/topCase | isAtBottom = bottomCase
                         | otherwise  = topCase
    lblTabY         =                      3 /|/ 60
    lblTabPosition  = Position left $ bottom /|/ top
    lblTabWidth     =        (lblPageX - 60) /|/ 20
    lblTabHeight    =                     20 /|/ (pageHeight - 80)
    lblTabMargin    =             Margin 0 3 /|/ Margin 3 0

You definitely do not want to do the check on isAtBottom multiple times, redundant code is always bad, regardless of which syntax you use. But when you do just need a single decision based on a simple boolean value, I would stick to the standard if rather than defining custom operators.

OTHER TIPS

in next(?) GHC at Data.Bool we'll have a bool function:

bool :: a -> a -> Bool -> a
bool f _ False = f
bool _ t True  = t

And your example we could rewrite:

lblTabX = bool 3 5 isAtBottom

Whoah, get rid of all those conditionals. You have a bunch of related settings which in one context will have one set of values, in another context another set.

data TabConfig = TabConfig { tabX, tabY, tabWidth, tabHeight :: Int,
                             tabPosition :: Position,
                             tabMargin :: Margin }

(You don't have to use record syntax; I'm doing it for clarity)

Now you just need to provide your functions with the appropriate TabConfig, from which they can extract the values. You can provide a defaultConfig function...

defaultConfig :: Context -> TabConfig
defaultConfig c = TabConfig { 5, 3, 20, (pageHeight c) - 80,
                              Position left bottom, Margin 3 0 }

You can also have

bottomConfig :: Context -> TabConfig
bottomConfig c = TabConfig { 3, 60, (pageX c) - 60, 20, Position left top, Margin 0 3 }

Then you just provide the appropriate TabConfig in the appropriate context. (Hopefull, you worked out that Context is another record-style type and of course you can be playing the same tricks with that).

If there are more parameters to TabConfig and being at bottom only changes some, you can do

bottomConfig c = let def = defaultConfig c
                 in def { tabX = 3, tabY = 60, ...

Just build the right configuration for the current context and make it available to your functions. Why should your table-drawing functions have to know about the logic you use to decide how their position on the page constrains their dimensions? What happens when new conditions impose extra constraints? Mixing them up is horrible. Separation of concerns is at least as important to functional programming as in OO. K

Of course, what I'm trying to hint at is that you want the Reader monad. But the Reader monad is so simple that you don't have to know you're using it, if monads are still daunting to you.

Pattern matching is the main syntax for choice, most everything else is expressed in terms of it. (if is defined in terms of an equivalent case expression for example).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top