我已经开始解决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''的方程有不同数量的参数
,有没有办法使用定义我的新函数的合成类型,同时还添加一些特定的行为?
答案 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 a
和initMay :: [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
也会提供lastNote
和initNote
。
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."