If fail

What is the equivalent of a Haskell pattern if it fails in imperative languages, for example:

function f (arg, result) { if (arg % 2 == 0) { result += "a" } if (arg % 3 == 0) { result += "b" } if (arg % 5 == 0) { result += "c" } return result } 
+7
haskell
source share
7 answers

Instead of using the State monad, you can also use the Writer monad and use an instance of String Monoid (really [a] Monoid ):

 import Control.Monad.Writer f :: Int -> String -> String f arg result = execWriter $ do tell result when (arg `mod` 2 == 0) $ tell "a" when (arg `mod` 3 == 0) $ tell "b" when (arg `mod` 5 == 0) $ tell "c" 

What I find quite concise, clean and simple.


One advantage that the State monad has is that you can reorder the order in which the union occurs by simply rearranging the lines. For example, if you want to run f 30 "test" and exit "atestbc" , all you have to do is swap the first two lines of do :

 f arg result = execWriter $ do when (arg `mod` 2 == 0) $ tell "a" tell result when (arg `mod` 3 == 0) $ tell "b" when (arg `mod` 5 == 0) $ tell "c" 

If in the State monad you will need to change the operation:

 f arg = execState $ do when (arg `mod` 2 == 0) $ modify ("a" ++) when (arg `mod` 3 == 0) $ modify (++ "b") when (arg `mod` 5 == 0) $ modify (++ "c") 

Thus, instead of having to relate between the order of execution and the order in the output line, you need to carefully study the actual operations (there is a subtle difference between (++ "a") and ("a" ++) ), whereas the Writer code very clear at first glance, in my opinion.


As @JohnL pointed out, this is not a very efficient solution, since concatenation on Haskell Strings not very fast, but you can use Text and Builder quite easily to get around this:

 {-# LANGUAGE OverloadedStrings #-} import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as T import qualified Data.Text.Lazy.Builder as B import Control.Monad.Writer f :: Int -> Text -> Text f arg result = B.toLazyText . execWriter $ do tellText result when (arg `mod` 2 == 0) $ tellText "a" when (arg `mod` 3 == 0) $ tellText "b" when (arg `mod` 5 == 0) $ tellText "c" where tellText = tell . B.fromLazyText 

And therefore, there is no real change in the algorithm other than conversion to more efficient types.

+14
source share

A function can be written quite briefly, provided that we are ready to somewhat overshadow the logic of the original imperative version:

 f :: Int -> String -> String f arg = (++ [c | (c, n) <- zip "abc" [2, 3, 5], mod arg n == 0]) 

Monad insights perfectly reproduce the original logic:

 {-# LANGUAGE MonadComprehensions #-} import Data.Maybe import Data.Monoid f :: Int -> String -> String f arg res = maybe res (res++) $ ["a" | mod arg 2 == 0] <> ["b" | mod arg 3 == 0] <> ["c" | mod arg 5 == 0] 

However, this is not a very commonly used language extension. Fortunately for us (in the comments on the tip hour for Örjan Johansen), for the list monad there is already a built-in saxophone for understanding, which we can also use here:

 f :: Int -> String -> String f arg res = res ++ ['a' | mod arg 2 == 0] ++ ['b' | mod arg 3 == 0] ++ ['c' | mod arg 5 == 0] 
+10
source share

Using the state monad as a comment by John Dvorak:

 import Control.Monad.State f :: Int -> String -> String f arg = execState $ do when (arg `mod` 2 == 0) $ modify (++ "a") when (arg `mod` 3 == 0) $ modify (++ "b") when (arg `mod` 5 == 0) $ modify (++ "c") 
+6
source share

I think the short answer is that the approach to failure in Haskell is Monoids . Whenever you want to combine many things into one thing, think Monoids . Adding is a great example:

1 + 2 + 4 + 0 + 3 = 10.

When adding numbers, the value is of type no-op 0 . You can always add it, and it will not change the result. Monoids generalize this concept, and Haskell names the value no-op mempty . This is how you remove elements from your combination (in your example, you drop values ​​that are not shared evenly). + - combiner. Haskell calls it mappend . There is an abbreviated character for this: <> .

Multiplication is a monoid, and mempty is 1 , the adder * .

Strings are also monoids. mempty value is "" , adder ++ ;

So here is a very simple implementation of your function using monoids:

 import Data.Monoid f :: Int -> String -> String f arg str = str <> modsBy 2 "a" <> modsBy 3 "b" <> modsBy 5 "c" where modsBy nv = if arg `mod` n == 0 then v else mempty 

The optimal thing is that since monoids generalize the concept, you can easily generalize this function so that it creates any monoid, not just a string. You can, for example, pass to the list of dividers, monoid pairs and some initial monoid to start with, and whenever the divider is evenly divided, you add a monoid:

 f :: Monoid a => Int -> a -> [(Int, a)] -> a f arg initial pairs = initial <> mconcat (map modsBy pairs) where modsBy (n, v) = if arg `mod` n == 0 then v else mempty 

mconcat simply merges the list of monoids together.

So, your initial example can now be run, for example:

 > f 10 "foo" [(2,"a"), (3,"b"), (5,"c")] "fooac" 

But you can just as easily create a number:

 > f 10 1 [(2,1), (3,2), (5,3)] 5 

One of the great things about Haskell is capturing and summarizing many of the concepts that I did not even imagine were there. Monoids come very conveniently and whole application architectures can be created on them.

+6
source share

There are several ways to do this. The only thing you can do is to represent each if as a function from Int -> Maybe Char , and then combine the Maybe Char list into the final string:

 maybeMod :: Int -> a -> Int -> Maybe a maybeMod dvi = if i `mod` d == 0 then Just v else Nothing f :: Int -> String fi = mapMaybe ($ i) [maybeMod 2 'a', maybeMod 3 'b', maybeMod 5 'c'] 
+5
source share

IMO, you must completely solve this problem. You are testing a series of very similar conditions; they should be stored together, and not spread over a bunch of conventions without obvious relationships. Why not include them in the list! Since each mod parameter must trigger a different signal symbol, you need a list of associations. So you start with [(2,'a'),(3,'b'),(5,'c')] . (if it is more, for example, 10, use take 10 $ zip primes ['a'..] with a list of all primes!)

Now we need each of the records to decide: if the given number is divided by prime, return the character, otherwise do not add anything. This is a very concise list comprehension:

 f :: Int -> String f arg = [ signal | (signal, n) <- [(2,'a'),(3,'b'),(5,'c')] , arg `mod` n == 0 ] 
+4
source share
 import Control.Arrow f :: Int -> String -> String f arg = (if arg `mod` 2 == 0 then (++"a") else id) >>> (if arg `mod` 3 == 0 then (++"b") else id) >>> (if arg `mod` 5 == 0 then (++"c") else id) 

In this case >>> is just flip (.) .

+1
source share

All Articles