Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is writing helper functions for state passing style idiomatic in Haskell?

Tags:

idioms

haskell

I'm working on some code for a card game:

splitCards_ (x:xs) (a,b,c) | isGold x   = splitCards xs (a:x,b,c) 
                           | isAction x = splitCards xs (a,b:x,c)
                           | isVP x     = splitCards xs (a,b,c:x)
splitCards_ [] (a,b,c) = (a,b,c)

splitCards xs = splitCards_ xs ([],[],[]) 

Essentially, taking a list of cards, and splitting it into three different lists depending on the type of the card. splitCards_ represents state updates by recursively updating it's parameters, then splitCards (the actual function) is used to always start the computation with the three lists of specific types of cards empty.

I believe this is called state-passing style, and I'm pretty sure that's perfectly idiomatic, but I'm more concerned with the fact that I have to define a helper function splitCards_ in order to get this to work the way I want. Is creating helper functions like this idiomatic Haskell? Is there a cleaner way to write this? Are there naming conventions preferable to just putting an underscore on the end of the name of the helper function?

like image 204
Nathan BeDell Avatar asked Nov 01 '14 02:11

Nathan BeDell


1 Answers

Yes, this is a perfectly idiomatic style. It's a classic way to make a function tail recursive. (Although that's less important and slightly more nuanced in Haskell).

Moreover, making helper functions is definitely good and a key part of many common patterns. If you feel that something is more natural factored out, go for it! Unless it's taken to an extreme, it helps readability.

The main suggestion I have is putting the helper function into a where clause. This way, it will only be visible in the scope of the main function which makes it clear that it's just a helper. The name you give the helper function is less important; splitCards_ is fine, but splitCards' (pronounced "splitCards prime") and go (a generic name for helper functions) would be more common. So I would rewrite your code something like this:

splitCards xs = go xs ([],[],[]) 
  where go (x:xs) (a, b, c) | isGold x   = go xs (a:x,b,c) 
                            | isAction x = go xs (a,b:x,c)
                            | isVP x     = go xs (a,b,c:x)
        go [] (a, b, c) = (a, b, c)

Note that these are just cosmetic changes—what you were doing is fundamentally sound.

The two naming options (go vs splitCards') is just a matter of preference.

I personally like go because, once you're used to the convention, it clearly signals that something is just a helper function. In a sense, go is almost more like syntax than a function of its own; it's meaning is purely subordinate to the splitCards function.

Others do not like go because it is a little cryptic and somewhat arbitrary. It might also make the recursive structure of your code less clear because you're recursing on go and not the function itself.

Go for whatever you think looks best.

like image 74
Tikhon Jelvis Avatar answered Dec 19 '22 23:12

Tikhon Jelvis