在添加其他行为的同时将新功能定义为功能组合

时间:2018-03-09 17:22:44

标签: haskell functional-programming

我已经开始解决99 problems in Haskell,对于第二个问题,它给出了以下解决方案:

myButLast' :: [a] -> a
myButLast' = last . init

如果我们给这个函数提供空列表,我们得到并且错误,但是,我想打印一个特定的错误

myButLast' [] = error "The list has to have at least 2 elements!"
myButLast' [x] = error "The list has to have at least 2 elements!"

但是当我将这些行添加到代码中时,我得到了

  

'myButLast''的方程有不同数量的参数

,有没有办法使用定义我的新函数的合成类型,同时还添加一些特定的行为?

5 个答案:

答案 0 :(得分:3)

你可以做的最好的事情可能就是以下内容,其中错误检查被移动到{{{ 1}}子句:

go

如果我们使用类型签名单独定义where,这可能有助于更清楚地说明发生了什么。此外,如果您发现下划线令人困惑,我已将其替换为myButLast :: [a] -> a myButLast = go (last . init) where go _ [] = bad go _ [x] = bad go f xs = f xs bad = error "The list has to have at least 2 elements!"

go

在这里,您可以看到f是一个带有两个参数的函数,第一个本身是类型myButLast :: [a] -> a myButLast = go (last . init) go :: ([a] -> a) -> [a] -> a go f [] = bad go f [x] = bad go f xs = f xs bad = error "The list has to have at least 2 elements!" 函数,第二个是类型列表go

[a] -> a模式的上述定义与第二个参数(列表)匹配。如果列表为空或单个,则[a]的结果只是go(错误消息),而不管函数go。否则(如果列表至少有两个元素),bad的结果只是将第一个参数(该函数f)应用于列表go f xs

这是如何工作的?好吧,让我们看看如果我们将f应用于列表会发生什么。我使用了符号"≡"这里显示Haskell表达式的等价性和注释,解释为什么它们是等价的:

xs

如果我们将它应用于"坏"列表,唯一的区别是从myButLast

的定义匹配的模式
myButLast [1,2,3]
-- by the definition of "myButLast"
≡ go (last . init) [1,2,3]
-- by the definition of "go", third pattern w/
--    f ≡ last . init
--    xs = [1,2,3]
≡ (last . init) [1,2,3]   -- this is just f xs w/ the pattern substitutions
-- because of your original, correct answer
≡ 2

另外,另一种看待go的另一种方式是它是一个功能:

myButLast [1]
-- by the definition of "myButLast"
≡ go (last . init) [1]
-- by the definition of "go", second pattern w/
--    f ≡ last . init
--    x = 1
≡ bad
-- gives an error message by definition of "bad"

因为函数应用程序箭头go是右关联的,所以此类型签名与go :: ([a] -> a) -> ([a] -> a) 完全相同。关于这一点的巧妙之处在于,现在很明显->采用([a] -> a) -> [a] -> a类型的函数(例如go)并返回类型为[a] -> a的另一个函数(例如last . init)。也就是说,[a] -> a是一个变换器,它为现有函数添加了额外的行为以创建一个新函数,这正是您在原始问题中所要求的。

实际上,如果你稍微概括一下类型签名,以便myButLast可以对一个列表的函数进行操作,无论它返回什么:

go

这仍然有效。然后,您可以在需要长度为2的列表的任何内容上使用此相同 go,无论它返回什么。例如,如果您有一个原始实现来返回列表的最后两个元素:

go :: ([a] -> b) -> [a] -> b
go _ [] = bad
go _ [x] = bad
go f xs = f xs

你可以把它重写为:

go

为空和单例列表案例添加错误处理。

在这种情况下,您可能希望将lastTwoElements :: [a] -> [a] lastTwoElements = (!! 2) . reverse . tails -- tails from Data.List 重命名为lastTwoElements :: [a] -> [a] lastTwoElements = go ((!! 2) . reverse . tails) go或其他内容......

答案 1 :(得分:2)

在解决方案中使用显式参数:

try
{
   var req = WebRequest.Create(photoUrl);

   using (var response = req.GetResponse())
   {
     using (var stream = response.GetResponseStream())
     {
       if (stream != null)
       {
         var image = Image.FromStream(stream);
       }
     }
   }
 }
 catch (Exception ex)
 {
   // handle exception
 }

现在,您可以在该行上方添加特殊情况。

原始解决方案使用无点样式myButLast' x = last (init x) 来避免提及last . init参数。但是,如果必须添加其他方程式,则需要使参数显式化。

移动
x

fun :: A -> B
fun = something

称为eta-expansion,是Haskell代码的常见转换。第一种风格通常称为无点(或者,开玩笑地,无点),而第二种风格称为有点。在这里"指向"指变量fun :: A -> B fun a = something a

答案 2 :(得分:1)

有些回避原始问题,但您可能对safe包感兴趣,可以执行此类任务。通常,您应该努力使用不会引发错误的总功能。在这种情况下,这意味着使用lastMay :: [a] -> Maybe ainitMay :: [a] -> Maybe a之类的内容,如果给出一个空列表,则只返回Nothing。它们可以使用<=<中的Control.Monad来撰写。

import Safe

myButLast :: [a] -> Maybe a
myButLast = lastMay <=< initMay

然后

> myButLast []
Nothing
> myButLast [1]
Nothing
> myButLast [1,2]
Just 1

如果您确实需要错误消息,Safe也会提供lastNoteinitNote

myButLast = let msg = "Need at least 2 elements" in (lastNote msg . initNote msg)

答案 3 :(得分:1)

您通常可以简单地编写一个具有附加行为的附加功能:

myButLast' :: [a] -> a
myButLast' = last . init . assertAtLeastTwo
  where assertAtLeastTwo xs@(_:_:_) = xs
        assertAtLeastTwo _ = error "The list has to have at least 2 elements!"

这里我们添加了一个函数来检查我们想要引发错误的条件,否则只是简单地返回它的输入,这样其他函数就可以对它起作用,就像assertAtLeastTwo不存在一样。

另一种允许您清楚地突出显示错误情况的替代方案是:

myButLast' :: [a] -> a
myButLast' [] = error "The list has to have at least 2 elements!"
myButLast' [x] = error "The list has to have at least 2 elements!"    
myButLast' xs = go xs
  where go = last . init

您在最初编写时执行错误检查,但主要定义只是遵循实现函数go,然后可以使用合成无点定义。

或者你当然可以从上面内联go,然后:

myButLast' xs = (last . init) xs

正弦函数的组合本身就是一个表达式,可以直接用作更大的表达式作为函数。实际上,一种相当常见的风格是使用$运算符编写“以一系列函数然后应用于此参数”的形式的代码,使用myButLast' xs = last . init $ xs 运算符:

def getPosFloat(prompt, n1, n2):
    while True:
        n = input(prompt)
        try:
            n = float(n)
            if n < n1 or n > n2:
                print('Error. Input was out of range')
            else:
                break
        except:
            print('Error. Please enter a number')
    return n

答案 4 :(得分:1)

如果您使用包装纸,您可以充分利用这两个世界,并在两者之间实现明确的分离。无论有没有包装,都可以使用强大的错误检查和报告以及使用的vanilla函数。 有趣的是,vanilla函数报告'last'不能处理给定一个元素列表的空列表,并且'init'在给定空列表时不能处理空列表。

mbl2 = last . init
mbl xs = if length xs < 2 then error errmsg else mbl2 xs
    where errmsg = "Input list must contain at least two members."