【问题标题】:Elegant way to get non empty list or `[[]]` (default value) in Haskell在 Haskell 中获取非空列表或“[[]]”(默认值)的优雅方式
【发布时间】:2021-01-23 08:39:10
【问题描述】:

是否可以简化这个函数的定义,如果l为空则返回[[]],否则返回l

f :: Eq t => [[t]] -> [[t]]
f l = if l == [] then [[]] else l

例如。在 python 中我可以这样做:

f = lambda l: l or [[]]

我想到了applicative:

f l = l <|> [[]]

但如果l非空,它会附加[]

有没有什么方法可以在 Haskell 中更优雅地做到这一点?

【问题讨论】:

  • 您可以做的一个小改进是将l == [] 替换为null l。更重要的是,我想你可以使用f = bool &lt;$&gt; id &lt;*&gt; (const [[]]) &lt;*&gt; null,但这是更“优雅”还是更模糊,还有待商榷。 Python 代码工作的唯一原因是空列表自动转换为“假”,因此可以与逻辑运算符一起使用; Haskell 的类型不是那么弱,所以在 Haskell 中找到这样一个优雅的解决方案会有点棘手。
  • 嗯,刚刚想到了另一种可能的解决方案,利用Data.List.NonEmpty:toList . fromMaybe (pure []) . nonEmpty。但同样,它比优雅更令人困惑。

标签: list haskell default-value


【解决方案1】:

当然可以进行一些改进,尽管我不会将它们中的任何一个称为简化。首先,我们可以通过使用模式匹配来摆脱Eq 约束:

f :: [[a]] -> [[a]]
f [] = [[]]
f x = x

或者我们可以用另一种方式摆脱它,使用foldr

f :: [[a]] -> [[a]]
f x = foldr (const (const x)) [[]] x

现在我们实际上不再需要输入是一个列表了。它可以是任何可折叠的,但它必须与输出类型相同。但是我们需要输出是一个列表的唯一原因是我们可以从中构建一个单例。所以我们毕竟不需要专门列出 - Applicative 可以做的任何事情:

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 ~ Maybea ~ Ordering。然后我们的函数就像这样(内联来自相关类型类实例的定义):

f :: Maybe Ordering -> Maybe Ordering
f (Just result) = Just result
f Nothing = Just EQ

有点奇怪的功能(打开 Maybe 比将其放入 Just 更自然),但我认为不会比您的原始功能更奇怪(为什么 [[]][] 更好无论如何?)。

【讨论】:

    【解决方案2】:

    如果您只需要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` [[]]
    

    【讨论】:

      【解决方案3】:

      您也可以为此使用ZipList

      > ZipList [[1, 2, 3]] <|> ZipList [[]]
      ZipList {getZipList = [[1, 2, 3]]}
      
      > ZipList [[1, 2], [3]] <|> ZipList [[]]
      ZipList {getZipList = [[1, 2], [3]]}
      
      > ZipList [[]] <|> ZipList [[]]
      ZipList {getZipList = [[]]}
      
      > ZipList [] <|> ZipList [[]]
      ZipList {getZipList = [[]]}
      

      【讨论】:

        【解决方案4】:
        foo :: [[t]] -> [[t]]
        foo xs  =  head  $  (xs <$ xs)  <|>  [ [[]] ]
              -- take one of
              --         either `xs`    OR    the default
        

        但请注意类型。它只适用于[[t]] 列表,因为这是[[]] 的类型。

        它可能会让人觉得有点混淆,除非您已经习惯了 xs &lt;$ xs 技巧(与 [xs | _ &lt;- xs] 相同)。

        【讨论】:

          【解决方案5】:

          我不确定你所说的优雅是什么意思,但是如果你想用更接近 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 -&gt; [[t]]。最后,运算符&amp;$ 的翻转版本——也就是说,它先接收参数,然后接收函数。这里,我们提供null l作为参数,函数为l `bool` [[]]


          不幸的是,: 符号在 Haskell 中是保留的。另一方面,? 符号不是。所以,如果你对&amp; 没问题,我们至少可以重新定义bool 并实现:

          (?) = bool
          
          f :: [[t]] -> [[t]]
          f l = null l & l ? [[]]
          

          它当然很简洁,类似于其他语言的简写 if 语句。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-11-16
            • 1970-01-01
            • 2020-09-04
            • 2013-08-13
            • 2011-12-30
            • 1970-01-01
            相关资源
            最近更新 更多