问题描述
是否可以简化此函数的定义,如果 [[]]
为空则返回 l
,否则返回 l
?
f :: Eq t => [[t]] -> [[t]]
f l = if l == [] then [[]] else l
例如。在 python 中,我可以这样做:
f = lambda l: l or [[]]
我想到了应用:
f l = l <|> [[]]
但在非空 []
l
有没有办法在 Haskell 中更优雅地做到这一点?
解决方法
如果您只需要 Eq
来检查列表是否为空,那您就错了。
f :: [[a]] -> [[a]]
f [] = [[]]
f x = x
或者,如果您更喜欢 if-then-else:
f :: [[a]] -> [[a]]
f x = if null x then [[]] else x
或者更一般地说:
class Fallible a where
failed ∷ a -> Bool
instance Fallible [a] where
failed = null
orelse ∷ Fallible a => a -> a -> a
orelse x y = if failed x then y else x
f :: [[a]] -> [[a]]
f x = x `orelse` [[]]
,
当然可以进行一些改进,尽管我不会将它们称为简化。首先,我们可以通过使用模式匹配来摆脱 <textarea [formControl]="getFormContentControl('body')"></textarea>
<markdown [data]="getFormContentControl('body').value" ngPreserveWhitespaces></markdown>
约束:
Eq
或者我们可以通过使用 f :: [[a]] -> [[a]]
f [] = [[]]
f x = x
以另一种方式摆脱它:
foldr
现在我们实际上不再需要输入为列表了。它可以是任何可折叠的,但它必须与输出类型相同。但是我们需要输出是一个列表的唯一原因是我们可以从中构建一个单例。所以我们真的不需要专门列出毕竟 - Applicative 可以做的任何事情:
f :: [[a]] -> [[a]]
f x = foldr (const (const x)) [[]] x
最后,我们可以尝试对最后一个 f :: (Foldable f,Applicative f) => f [a] -> f [a]
f x = foldr (const (const x)) (pure []) x
进行概括,使该函数适用于尽可能多的地方。 []
的一些明显选择是 Monoid 或 Alternative - 其中任何一个都有空的概念。我可能会选择 Monoid 作为最简单的,屈服:
[]
所有这些抽象给我们带来了什么?我们现在可以对不同的数据类型进行类似的操作。例如,假设我们任意选择f :: (Foldable f,Applicative f,Monoid a) => f a -> f a
f x = foldr (const (const x)) (pure mempty) x
和f ~ Maybe
。然后我们的函数就像这样(内联相关类型类实例的定义):
a ~ Ordering
有点奇怪的函数(打开 Maybe 比把它放在 f :: Maybe Ordering -> Maybe Ordering
f (Just result) = Just result
f Nothing = Just EQ
中更自然),但我认为没有比你原来的函数更奇怪(为什么 Just
更好还是比[[]]
?)。
您也可以使用 ZipList
:
> ZipList [[1,2,3]] <|> ZipList [[]]
ZipList {getZipList = [[1,3]]}
> ZipList [[1,2],[3]] <|> ZipList [[]]
ZipList {getZipList = [[1,[3]]}
> ZipList [[]] <|> ZipList [[]]
ZipList {getZipList = [[]]}
> ZipList [] <|> ZipList [[]]
ZipList {getZipList = [[]]}
,
foo :: [[t]] -> [[t]]
foo xs = head $ (xs <$ xs) <|> [ [[]] ]
-- take one of
-- either `xs` OR the default
注意类型。它仅适用于 [[t]]
列表,因为这是 [[]]
的类型。
它可能会让人感觉有点混乱,除非您已经习惯了 xs <$ xs
技巧(与 [xs | _ <- xs]
相同)。
我不确定你所说的优雅是什么意思,但如果你想以更接近 Python 的风格来编写它,你可以这样写:
import Data.Function ((&))
import Data.Bool (bool)
f :: [[t]] -> [[t]]
f l = null l & l `bool` [[]]
bool
函数首先将 else/False
情况作为参数,然后是 then/True
情况,最后是它正在检查的布尔值。在这里,我们使用中缀表示法为其提供前两个参数。换句话说,l `bool` [[]]
的类型为 Bool -> [[t]]
。最后,运算符 &
是 $
的翻转版本——也就是说,它先接受参数,然后是函数。这里,我们提供null l
作为参数,函数是l `bool` [[]]
。
不幸的是 :
符号在 Haskell 中是保留的。另一方面,?
符号不是。因此,如果您对 &
没问题,我们至少可以重新定义 bool
并实现:
(?) = bool
f :: [[t]] -> [[t]]
f l = null l & l ? [[]]
它确实简洁,类似于其他语言的简写 if 语句。