X Y Z
X Y Z

Reputation: 65

Statement sequencing in Haskell?

i am trying to compile a quite simple haskell program. Either I don't know how to sequence statements (expressions) in Haskell, or there isn't a way to do it.

I essentially want a function of the form:

f = <do stuff 1>
    <do stuff 2>
    ...

the following was supposed to simulate a pattern of

<do stuff>
<tail recursion>

but failed to compile:

go 0 = 0
go i =  case i of
            1 -> 2
            _ -> 3
        go (i-1)

This doesn't work either (simpler, no recursion):

go i =  1
        2

The code directly above compiles, but while running I get the cryptic:

No instance for (Num (a0 -> t0))
  arising from a use of `go'
Possible fix: add an instance declaration for (Num (a0 -> t0))
In the expression: go 2
In an equation for `it': it = go 2

Am I having a problem with indention? A problem with not sequencing correctly? Or is there no sequencing capability? If no sequencing, how would you straightforwardly do

<do stuff>
<tail recursion>

?


Thanks for the responses.

Hard to believe a function in Haskell is basically limited to one "expression" or "statement" or whatever you want to call it. How would you solve the following contrived toy example (written in erlang, but could easily be translated direcly into prolog or a whole host of other langs):

count([], X) -> X;
count([_|B], X) ->
    Y = X+1,
    count(B, Y).

An example run:

erlang> count([a,b,c,d,e],0).
5

The 2nd clause fits the pattern I described earlier of:

<do stuff>
<tail recursion>

I guess this particular example maps onto the Haskell "let... in" someone described here. But what if there needs to be 10 "do stuffs", or 20, or more? And is this "let... in" guaranteed to be tail recursive (the above example is guaranteed to be in erlang, prolog, scheme, etc).

Is there a way I can simply "string together" or "sequence" a bunch of statements or expressions? In prolog or erlang the sequencing is accomplished by the comma (see toy example above). In c/c++ it is accomplished by the semicolon. I thought the Haskell operator was simply whitespace, but perhaps it just isn't done in this language?

Upvotes: 3

Views: 3680

Answers (7)

Roland Puntaier
Roland Puntaier

Reputation: 3511

A function mathematically is defined by its unique map from input to output. A sequence is created by forwarding output to a new function. So the natural way to do sequences in Haskell is function composition ... f2 . f1. Sometimes a function returns more than what is needed for the next step. So you need let or where to extract what you want to forward to the next step.

forward only refers to from output to input. From input to output it is a map between different variables (a function).

Nothing is ever changed. No side effect.

do notation is also function composition. You might think something (Monad) is handled through the functions and possibly changed, but it is not changed. Instead of a change a modified copy is constructed. Well, ideally, because IO-like Monads will do changes to the outside world.

Upvotes: 0

Greg Bacon
Greg Bacon

Reputation: 139431

Update: Be careful that you’re not writing Scheme programs in Haskell. Accumulating parameters are rare in idiomatic Haskell, which uses laziness rather than tail recursion.

For example, the definition of count in Haskell will resemble

count [] = 0
count (x:xs) = 1 + count xs

You might object that the source of length looks more Scheme-ish

-- | /O(n)/. 'length' returns the length of a finite list as an 'Int'.
-- It is an instance of the more general 'Data.List.genericLength',
-- the result type of which may be any kind of number.
length                  :: [a] -> Int
length l                =  len l 0#
  where
    len :: [a] -> Int# -> Int
    len []     a# = I# a#
    len (_:xs) a# = len xs (a# +# 1#)

but this is low-level, non-idiomatic code. The referenced genericLength has the same structure as count above and has broader applicability when compared to length.

genericLength           :: (Num i) => [b] -> i
genericLength []        =  0
genericLength (_:l)     =  1 + genericLength l

“Do this and then do that” in Haskell is expressed as function composition in pure code. For example, to compute the length of the longest sublist, we’d compute the sublists’ lengths and then the maximum over those lengths. In Haskell, that’s expressed

Prelude> :t maximum . map length
maximum . map length :: [[a]] -> Int

or

Prelude> :m + Data.List 
Prelude Data.List> :t maximum . map genericLength
maximum . map genericLength :: (Ord c, Num c) => [[b]] -> c

Note: laziness adds nuance, but the general point holds.

Even in “imperative” code inside IO such as

main :: IO ()
main = do
  putStr "Hello "
  purStrLn "world!"

is function composition under the covers because it has the same semantics as

main :: IO ()
main = putStr "Hello " >>= \_ -> putStrLn "world!"

Perhaps an example that uses the list monad will make this clearer. Say we want all Pythagorean triples (a,b,c) such that no component is greater than some maximum n.

import Control.Monad (guard)

triples :: Integer -> [(Integer,Integer,Integer)]
triples n = do
  a <- [1 .. n]
  b <- [a .. n]
  c <- [b .. n]
  guard $ a*a + b*b == c*c
  return (a,b,c)

As you can see, we must first choose a candidate value of a, then of b, and so on.

The compiler transforms this code to explicitly use >>=, the monadic bind combinator.

-- indented to show nested scopes
triples_bind :: Integer -> [(Integer,Integer,Integer)]
triples_bind n =
  [1 .. n] >>= \a ->
    [a .. n] >>= \b ->
      [b .. n] >>= \c ->
        (guard $ a*a + b*b == c*c) >>= \_ ->
          return (a,b,c)

Inside the list monad, >>= is concatMap, so the above is identical to

triples_stacked :: Integer -> [(Integer,Integer,Integer)]
triples_stacked n =
  concatMap (\a ->
    concatMap (\b ->
      concatMap (\c ->
        concatMap (\_ ->
          [(a,b,c)])
          (guard $ a*a + b*b == c*c))
        [b .. n])
      [a .. n])
    [1 .. n]

The indentation shows structure and is pleasing because it gives the impression of the Haskell logo that combines λ with >>=.

Yet another way to express the same semantics is

triples_cm n = concatMap step2 [1 .. n]  -- step 1
  where step2 a = concatMap step3 [a .. n]
          where step3 b = concatMap step4 [b .. n]
                  where step4 c = concatMap step5 (guard $ a*a + b*b == c*c)
                          where step5 _ = [(a,b,c)]

Haskell’s pattern matching already does case matching behind the scenes. Instead of

go 0 = 0
go i =  case i of
            1 -> 2
            _ -> 3
        go (i-1)

finish the pattern you started.

go 0 = 0
go 1 = go (2 - 1)
go _ = go (3 - 1)

Remember that variables in Haskell are immutable: they do not have destructive update as in C or Java. You get one shot at defining a variable’s value, and then you’re stuck with it. You might instead define go as

go 0 = 0
go x = let i = case x of 1 -> 2
                         _ -> 3
       in go (i - 1)

or

go 0 = 0
go x | x == 1    = go (2 - 1)
     | otherwise = go (3 - 1)

The examples above all typecheck but have the same big problem, namely that go terminates only when its argument is zero. You didn’t provide enough information for us to help with that problem. Tell us what it is you’re trying to do, and we can offer specific advice for your situation.

Upvotes: 1

Daniel Pratt
Daniel Pratt

Reputation: 12077

It is very good that you were able to provide an Erlang code example of what you mean by 'doing stuff'. Until now it's been a little vague. So long as the discussion is only about strict languages, it's okay to be somewhat vague on that point since the rules are fairly uniform and understood.

On the other hand, we are talking about Haskell, a language with 'lazy' evaluation, so it is good to be very specific about what 'doing stuff' means, otherwise there is likely to be confusion due to incorrect assumptions.

Here's a fairly direct translation of the Erlang function you provided in Haskell:

count ([],x) = x
count (_:b, x) =
    let y = x + 1
    in count (b, y)

As you can see, it's pretty much identical in structure to the Erlang version. As such, you may be wondering what is the big deal? If it's that easy to 'do stuff' in Haskell, then why is everyone making it sound so complicated?

One way to explain what is the big deal is to point out that the above Haskell function is equivalent to this one:

count ([],x) = x
count (_:b, x) = count (b, x + 1)

Also this one:

count ([],x) = x
count (_:b, x) = count (b, y) where y = x + 1

This is because the order in which Haskell evaluates expressions does not depend on the lexical order of those expressions. In general, Haskell will delay expression evaluation to the last possible moment.

Now when you think about composing this scheme of lazy evaluation together with side-effects (reading or writing files, sending output to the screen, etc.), it gets a little fuzzy. The order in which side-effects happen is generally very important. Fortunately, functions in Haskell that deal with side-effects generally do not perform the side-effecting action themselves. Rather they produce an 'action' value that describes the side-effect. Then there are other functions that can compose these 'action' values in a way that respects order. Then you just need something that will actually perform the side-effects that are described by the 'action' values...

Upvotes: 5

Dan Burton
Dan Burton

Reputation: 53665

Obligatory meme

In Haskell, you don't write functions that "do stuff". You write functions that compute a value. In that light, it makes no sense to structure a function in the way you suggest:

f = <do stuff 1>
    <do stuff 2>

Instead, you write an actual, math-y function, which is a single expression dependent on certain inputs:

f x y = blah x blah y blah

It might be convenient to break it up into sub-expressions

f x y = res1 + res2
  where res1 = x * 3
        res2 = y + 4

But in the end, it is just a single expression.


Monadic code written in do notation can appear to "do stuff" sequentially:

f = do
  thing1
  thing2

But this is just an illusion. It desugars into a single expression as well:

f = thing1 >> thing2

This expression states that f is a value which is the composition of thing1 and thing2. Now, IO code might compile to code that "does stuff" sequentially, but that's not what it means in Haskell. One does not simply "do stuff" in Haskell. Instead, you compose.

Upvotes: 25

Rijk
Rijk

Reputation: 612

I'm not sure got correctly what your function has to do. May be you need something like this:

go :: Int -> Int
go 0 = 0
go i = go (i-1)

if that function takes argument e.g. '3' it calculates go 3 -> go 2 -> go 1 -> go 0 -> 0 and get answer '0'

Now if you want some sequencing you can use keyword 'do', but I am sure it is now what you need. If you are trying to create some kind of 'variables' look at 'let' & 'in' statements :

someInput = 7

gogogo 0  = 0
gogogo n = 
    let
        gavk = n + 1
        kravk = gavk * 2
        out = kravk + 1
    in 
        out

main = 
   do
       putStrLn "this is sample do statement"
       putStrLn "gogogo:"
       putStrLn $ gogogo someInput 

UPD. edited mistypes

Upvotes: 0

dave4420
dave4420

Reputation: 47042

When you say "do stuff", what do you mean?

  • calculate something, transform data we already have?
  • interact with the user, read/write a file, download something from the web?

In the first case, use let...in:

quadraticRoots a b c = let discriminant = b * b - 4 * a * c
                           root = sqrt discriminant
                       in case compare discriminant 0 of
                               LT -> []
                               EQ -> [-b / (2 * a)]
                               GT -> [(-b + root) / (2 * a), (-b - root) / (2 * a)]

In the second case, use do notation:

main = do args <- getArgs
          case map read args of
               [a, b, c] -> putStrLn $ case quadraticRoots a b c of
                                            [] -> "no real roots"
                                            [x] -> "single root: " ++ show x
                                            [x1, x2] -> unwords ["two real roots:", show x1,
                                                                 "and", show x2]
               _ -> hPutStrLn stderr "Please provide three coefficients on the command line"

Upvotes: 1

fuz
fuz

Reputation: 92966

Basically, there is no way to do it. Haskell is a purely functional language, there is neither change nor order of statements. One way to simulate sequence are monads. There are plenty of tutorials in the internet, I personally advise you to read the monad-chapters of Learn You A Haskell For Great Good! or Real World Haskell. Another thing is: Sequential operations are not very idiomatic in Haskell. You should better start with something different.

Upvotes: 2

Related Questions