如何匹配Haskell中的类型,以使期望的类型与实际类型匹配?

时间:2016-09-08 04:57:12

标签: haskell functional-programming command-line-arguments fizzbuzz

尝试使用optparse-applicative在某些命令行中实现fizzBu​​zz。

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

我设置了三个命令行参数;两个除数(在fizzBu​​zz中通常是3和5),第三个是上限(通常是100),但是当我去编译时我得到一个错误说:

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

我的主要目标是使用三个命令行args打印出fizzBu​​zz系列。根据我的理解,我不喜欢为fizzBu​​zz提供额外的参数。试图理解为什么'我'不会在这里工作。

更新:

我认为这段代码更接近,因为它使用getArgs而不是optparse来处理命令行。还添加了一个列表以针对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

所以我的问题是我无法访问s1和s2变量以获得我的fizzBu​​zz。如何访问main范围之外的那些args?也许还有其他功能可以提供帮助吗?

1 个答案:

答案 0 :(得分:1)

  

如何访问main范围之外的那些args?

你不能。这正是范围的重点:内部变量保持在内部,很好地封装,防止任何难以跟踪的数据依赖。

实现访问s1 s2 s3目标的最简单方法是在范围内定义fizzbuzz :< / p>

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

然而,这不一定是最好的方法 - 你实际上创建了一个大的伪全局IO范围。请注意,这绝不像命令式语言中的全局变量那么危险,但至少如果你想在其他语境中使用fizzBuzz,这显然不是最佳的。

当然,正确的方法是将这些变量作为函数参数传递。很像你已经拥有它 - 我觉得很奇怪,你再次删除它。

所以,首先找出fizzBuzz的签名。它需要什么,它会产生什么?嗯,它需要所有这些除数。因此,给它一个Args参数实际上是很有意义的。但它还需要数字i,这只是Int。至于结果......为什么会IO ()? fizzBu​​zz是一个非常好的纯函数,产生一个字符串。所以......

fizzBuzz :: Args -> Int -> String
嗯,现在你很好。 定义实际上看起来完全正确。 (但一般来说,首先获得正确的类型签名更为明智,然后担心如何实际实现它。)

现在你只需要调用这个函数。在编写任何main函数之前,我先在GHCi中使用它:

$ 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"
...

如果您认为它正常,您可以将其包装到实际程序中。

请注意,Args数据结构与OptParse库实际上没有任何关系。该库对于使用适当的命令行程序是有意义的,但这在这里是有点过分的。然而,正如我已经展示的那样,使用Args类型将这些除数传递给函数是很有意义的。

我现在只使用getArgs来获取参数,并手动将它们包装到Args结构中:

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

你可能想知道read的所有内容是什么。好吧,getArgs为你提供了来自shell的命令行参数:as strings。但是你需要解释,即读取这些字符串为整数。请注意read非常不安全:如果输入不是有效的整数文字,程序将崩溃。对你来说可能不是问题,但这是optparse很好地为你解决的问题之一。

由于您根本不需要字符串形式的参数,因此您甚至可以在匹配各个参数之前read 所有

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