-1

Attempting to implement fizzBuzz with some command line using optparse-applicative.

import Options.Applicative
data Args = Args { firstDivisor :: Int,
                 secondDivisor :: Int,
                 upperBound :: Int }
fizzBuzz :: Args -> IO ()
fizzBuzz opts i
    | i `mod` firstDivisor opts == 0 && i `mod` secondDivisor opts == 0 = "fizzBuzz"
    | i `mod` firstDivisor opts == 0 = "fizz"
    | i `mod` secondDivisor opts == 0 = "buzz"
    | otherwise = show i
main :: IO ()
main = print fizzBuzz

I have it set up to take in three command line arguments; two divisors(in fizzBuzz usually 3 and 5), and the third being the upper limit(usually 100), but when I go to compile I get an error saying:

Couldn't match expected type ‘Int -> [Char]’
            with actual type ‘IO ()’
The equation(s) for ‘fizzBuzz’ have two arguments,
but its type ‘Args -> IO ()’ has only one

My main goal is to just print out the fizzBuzz series with the three command line args. From what I understand, it isn't liking that I supplied fizzBuzz with an extra parameter. Trying to understand why 'i' wouldn't work here.


UPDATE:

This code I feel is closer, as it addresses the command line with getArgs instead of optparse. Also added a list to run against fizzBuzz.

import System.Environment

main :: IO ()
main = do
    [s1,s2,s3] <- getArgs
    print m
m = [ fizzBuzz i| i <-[1..s3]]
fizzBuzz i
  | i `mod` s1 == 0 && s1 `mod` s2 == 0 = "fizzBuzz"
  | i `mod` s1 == 0 = "fizz"
  | i `mod` s2 == 0 = "buzz"
  | otherwise = show i

So my problem is that I cannot access the s1 and s2 variables in order to get my fizzBuzz goin. How can I access those args outside of the scope of main? Maybe there is another function that can help?

  • 5
    `fizzBuzz :: Args -> IO ()` says that `fizzBuzz` is a function taking an `Args` as a parameter and returning a `IO ()`. But then `fizzBuzz opts i` declares two arguments. And in all of the cases it tries to return a string. – user253751 Sep 08 '16 at 05:19
  • 1
    If you are a beginner, I'd go for a simpler approach and avoid optparse-applicative. Just use `[s1,s2,s3] <- getArgs` to get three strings from the args, convert them to numbers with `read`, and move on. For bonus points, later on, handle the wrong arguments case (not three / not numeric) using `reads` or `readMaybe`. But first, learn about how to use the IO monad using a good tutorial. – chi Sep 08 '16 at 12:32
  • You'll also need to somehow tell it to do all that for all the numbers in `[1..upperBound]`. – Gurkenglas Sep 08 '16 at 13:51
  • @chi okay i figured as much, def need to hit up more tutorials. in my update i use list comprehension like @Gurkenglas mentions in order to create a list to run against `fizzBuzz`. so with the updated code that i posted, my main problem i am running into the problem of `s1` and `s2` not being in scope. i feel like i am close here. – selfresonator Sep 08 '16 at 20:45
  • @selfresonator Add two parameters to `fizzBuzz`. – molbdnilo Sep 08 '16 at 21:22
  • @molbdnilo something like `fizzBuzz i j k | i mod`...etc.? when i do this i get that all three arguments are out of scope. now when i move the m line up into the correct scope(and change the `=` to `<-`), i still end up with the `Couldn't match expected type ‘IO String with actual type ‘[[Char]]’` error. I've been trying to understand the types better but I think I am mistaken when i write `main IO ()`, isnt that saying that main returns an action? How should I convert – selfresonator Sep 08 '16 at 22:12

1 Answers1

1

How can I access those args outside of the scope of main?

You can't. That precisely the point of a scope: variables inside stay inside, nicely encapsulated, preventing any hard-to-trace data dependencies.

The easiest way to accomplish the goal of accessing s1 s2 s3 is to simply define fizzbuzz in the scope of main:

main :: IO ()
main = do
   [s1,s2,s3] <- map read<$>getArgs  -- more on this later
   let fizzBuzz i
         | i `mod` s1 == 0 && s1 `mod` s2 == 0 = "fizzBuzz"
         | i `mod` s1 == 0 = "fizz"
         | i `mod` s2 == 0 = "buzz"
         | otherwise = show i
       m = [fizzBuzz i| i <-[1..s3]]
   print m

However, that's not necessarily the best approach – you'd essentially create one big pseudo-global IO scope. Mind, this is nowhere as dangerous as global variables in imperative languages, but at least if you want to also use fizzBuzz in some other context, this is clearly not optimal.

The correct way is, of course, to pass in those variables as function arguments. Quite like you already had it – I find it strange that you removed this again.

So, first of all figure out the signature of fizzBuzz. What does it need, and what does it yield? Well, it needs all those divisors. Hence it actually made a lot of sense to give it an Args argument. But it also needs the number i, which is simply an Int. As for the result... why would it be IO ()? fizzBuzz is a perfectly good pure function, yielding a string. So...

fizzBuzz :: Args -> Int -> String

Well, now you're fine. The definition actually looks completely right the way you've originally had it. (But generally, it's much more sensible to first get the type signature right, then worry about how to actually implement it.)

Now you just need to invoke this function. I'd first play around with it in GHCi a bit before writing any main function at all:

$ ghci FizzBuzz.hs
GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling Main             ( FizzBuzz.hs, interpreted )
Ok, modules loaded: Main.
*Main> fizzBuzz (Args 3 4 9) 3
"fizz"
*Main> fizzBuzz (Args 3 4 9) 5
"5"
...

If you think that it works right, you may wrap it into an actual program.

Note that the Args data structure doesn't really have anything to do with the OptParse library. That library makes sense to use for proper command-line programs, but it's overkill here. Nevertheless, it makes a lot of sense to use the Args type for passing those divisors into the function, as I've already showed.

I'd now just use getArgs for fetching the arguments, and manually wrap them into the Args structure:

module Main where

import System.Environment (getArgs)

main :: IO ()
main = do
   [s₁,s₂,s₃] <- getArgs
   let divisors = Args (read s₁) (read s₂) (read s₃)
   m = [fizzBuzz divisors i | i<-[1..read s₃]]
   print m

You may wonder what's up with all that read. Well, getArgs gives you the command-line parameters as they come from the shell: as strings. But you need to interpret, i.e. read those strings as integers. Note that read is pretty unsafe: if the input is not a valid integer literal, the program will crash. Probably not a problem for you right now, but this is one of the things that optparse would nicely solve for you.

Since you don't really need the parameters in string form at all, you might just as well read all of them before even matching out the individual parameters:

main :: IO ()
main = do
   [s₁,s₂,s₃] <- map read <$> getArgs
   let divisors = Args s₁ s₂ s₃
   m = [fizzBuzz divisors i | i<-[1..s₃]]
   print m
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • Is it intentional that `read` does not appear in the first solution? – chi Sep 08 '16 at 22:20
  • No, I overlooked it at the beginning. – leftaroundabout Sep 08 '16 at 22:26
  • thank you for clarifying some things i was wondering but not asking @leftaroundabout. figuring out the type signature for fizzbuzz was definitely in my way. now lets say i dont go with my second solutions work and go with the first. lets say ive got the correct type sig now, I get a `No instance for (Show(Args -> Int -> String))`. im guessing i need something like `instance show (some code)` – selfresonator Sep 08 '16 at 22:45
  • @selfresonator That means you are trying to print a function, instead of the result of the function. Check you are passing all the args to the function whose result you want to print. – chi Sep 08 '16 at 22:46
  • `read` doesn't seem a good way to deal with command-line arguments, even in a simple example. Why not use `traverse readMaybe`? – dfeuer Sep 08 '16 at 23:54
  • Yeah, but does that really improve anything? You'd still not get any understandable error message with that approach. If `read` didn't give quite to cryptic errors, then I'd say it would actually be fine to use it for parsing command-line options (a program with wrong cmd options may crash anyway, it just shouldn't do so in a totally inexplicable way). – leftaroundabout Sep 09 '16 at 09:00
  • @leftaroundabout took your advice here with getArgs, and got the solution i was looking for. optparse-applicative is definitely overkill. i initially wanted to put the fizzbuzz inside of the main do but, when going with my other solution, the compiler threw errors because of the scope issue that you so explicitly reminded me of. thank you very much, much appreciated. – selfresonator Sep 10 '16 at 01:19
  • @leftaroundabout btw what does the `<$>` do exactly? is that a haskell templates thing? i understand the usage of `$` in stead of typing parens everywhere. but im confused why it wouldnt work with just `$` in this case, in order to pass the getargs as as an arg to the result of `map read`. whats the difference between `$` and `<$>`? – selfresonator Sep 13 '16 at 00:28
  • `<$>`, as [Hayoo could have told you](https://hayoo.fh-wedel.de/?query=%3C%24%3E), applies a function to a value contained in a monadic action (or more generally in a functor). `f <$> m` is equivalent to `fmap f $ m` (or simply `fmap f m`). In case of IO, it allows you to apply a function to the result-value of some action (such as `getArgs`), without needing to first give that value any name. Instead of `do { y <- f <$> act; ... }` you could also write this: `do { x <- act; let y = f x; ... }`. But this introduces quite unnecessarily the variable `x` – if you never use it again, useless. – leftaroundabout Sep 13 '16 at 00:34