Is writing helper functions for a language that follows an idiomatic style in Haskell?

I am 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 breaking it into three different lists depending on the type of card. splitCards_ represents the status of updates by recursively updating its parameters, then splitCards (the actual function) is used to always start the calculation with three lists of certain types of cards empty.

I find this to be called a state transition style, and I'm sure it is completely idiomatic, but what worries me more is that I have to define the splitCards_ helper function to make it work as I want. Does helper functions create like this idiomatic Haskell? Is there a cleaner way to write this? Are naming conventions preferable to simply underscoring the end of a helper function name?

+5
source share
3 answers

Yes, this is a completely idiomatic style. This is a classic way to make tail function recursive. (Although this is less important and a bit more subtle in Haskell).

In addition, creating helper functions is certainly a good and key part of many common patterns. If you feel something more natural, check it out! If it is not taken to extremes, it helps to read.

The main suggestion I have is to put a helper function in the where clause. Thus, it will be visible only within the framework of the main function, which makes it clear that this is just an assistant. The name you give to the helper function is less important; splitCards_ great, but splitCards' (pronounced splitCards prime) and go (common name for helper functions) will 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 do sounds fundamentally.

Two naming options ( go vs splitCards' ) are just a matter of preference.

I personally like go , because as soon as you get used to the agreement, it clearly signals that something is just an auxiliary function. In a sense, go almost more like syntax than native function; this value is purely subordinate to the splitCards function.

Others do not like go , because it is a little mysterious and somewhat arbitrary. It can also make the recursive structure of your code less clear, because you are recursing to go , and not to the function itself.

Go for what you think is best.

+8
source

I have nothing to add to @Tikhon's answer, except that in this particular case you can use the partition function from Data.List :

 splitCards xs = let (as,bs) = partition isGold xs (cs,ds) = partition isAction bs in (as,cs,ds) -- (golds, actions, others) 

Due to lazy evaluation, it should have almost the same performance as your manual version.

+3
source

In general, a code like yours is idiomatic, especially if you clean it up a bit, as @Tikhon suggested. In the specific case, your recursion scheme looks like a left fold, so it is usually made explicit in the code. Based on @Tikhon text:

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

The first line may even be eta-tightened (not really needed in this case, IMHO, but ...)

 splitCards = foldl' separate ([],[],[]) where ... 
+1
source

Source: https://habr.com/ru/post/1205996/


All Articles