【问题标题】:How can I concisely match a list according to its latter half, and bind it's former half accordingly?如何根据列表的后半部分简洁匹配列表,并相应地绑定它的前半部分?
【发布时间】:2019-05-18 07:10:42
【问题描述】:

假设我有一个函数f :: String -> String 并且想要匹配表单的参数

_ ++ "bar"

其中_ 是我想返回的未指定字符串。换句话说,我想匹配foobarbazbar 之类的参数,并分别返回foobaz

使用ViewPatterns 可以如下实现:

{-# LANGUAGE ViewPatterns #-}

f :: String -> String
f x@(reverse . take 3 $ reverse -> "bar") = take (n-3) x
    where n = length x

...但这远非理想。主要是因为如果我决定要组合两个或多个这样的模式,事情会很快变得棘手。

理想情况下,我希望能够写出这样的东西:

f (x:"bar") = x

但不幸的是,这不是有效的 Haskell。

ViewPatterns 或其他扩展中是否有适当的解决方案?

【问题讨论】:

  • 正则表达式怎么样?它们可以包含结束字符串锚点,您可以只匹配初始段,例如“(.*)bar$”
  • 我不知道 Haskell 中的正则表达式。那会是什么样子?
  • 也许是时候写一个真正的解析器了。 (我敢打赌你问这个是因为它是一段更大的代码的一部分,实际上是在进行解析。)See also.
  • 打开一扇深入了解这个问题的窗口:f (x ++ y) = (x,y) 计算什么?

标签: haskell


【解决方案1】:

在内置 String 上,这是一个非常糟糕的主意,因为您的模式匹配结果非常昂贵。在其他字符串类型上,例如 TextByteString,您可以使用模式保护:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.Text as T

f :: Text -> Text
f x | Just x' <- T.stripSuffix "bar" x = ...

或者ViewPatterns(我不太喜欢):

{-# LANGUAGE OverloadedStrings, ViewPatterns #-}

import qualified Data.Text as T

f :: Text -> Text
f (T.stripSuffix "bar" -> Just x') = ...

【讨论】:

    【解决方案2】:
    stripSuffix :: (Eq a) => [a] -> [a] -> Maybe [a]
    stripSuffix needle = go <*> drop (length needle)
      where
        go xs [] = if xs == needle then Just [] else Nothing
        go (x:xs) (_:ys) = (x:) <$> go xs ys
    
    f (stripSuffix "bar" -> Just pref) = pref
    

    我没有对其进行过多测试,但这是一个简单的解决方案,不会引入额外的正则表达式/解析器机制。

    【讨论】:

      【解决方案3】:

      不要使用模式匹配。模式匹配通常很便宜并且匹配输入数据的结构。这是一个非常昂贵的模式,编写起来好像计算起来非常便宜。如果你想这样做,把它写成一个保护子句,在那里你可以清楚地说明发生了什么。

      【讨论】:

      • 我认为,当问题对性能没有任何兴趣时,出于性能原因而反对某些事情是错误的 - 过早的优化是万恶之源。
      • 随心所欲地做慢事。我的回答建议完全做那些慢的事情......但是在守卫中进行,在那里你可以看到它们很昂贵。
      • 当性能还不是问题时,这仍然是在考虑性能的情况下进行编码 - 谁在乎这很昂贵?你甚至不知道这段代码是否会以一种意味着阅读它的人不知道“模式匹配”的行为的方式被共享。
      【解决方案4】:

      几乎可以使用 TemplateHaskell - 更有经验的人可以改进这个答案。

      import Language.Haskell.TH
      import Language.Haskell.TH.Syntax
      
      (+++) :: Q Pat -> String -> Q Pat
      x +++ y = [p| ((\string -> splitAt (length string - length y) string) -> (x, $literal_pattern)) |]
        where literal_pattern = returnQ (LitP (StringL y))
      

      这可用于模式位置,您可以使用 TemplateHaskell 的 quasiquoting 将模式作为第一个参数传递:

      f $([p|x|] +++ "bar") = x
      

      相当烦人的是,我找不到比这更简洁的解释来说明如何将模式传递给 TemplateHaskell 拼接。

      【讨论】:

      • 我建议不要将 TH 用于此类琐碎的任务。除了损害可读性之外,它还会影响编译时间。此外,您的 +++ 完全错误 - 它忘记检查后缀是否匹配! f "hello" 高兴地返回 "he",而不是遇到非详尽案例错误。那是因为ViewPatterns 引入了一个新的y 变量,而不是断言它等于你的外部y。 IMO,这个解决方案的复杂性超过了小的语法好处。
      • 感谢您指出这一点 - 我对准引用机制有点天真。更新版本有效。但我不认为可读性或编译时间重要到足以打折这些解决方案,除非问的人这么说。问题是关于句法的好处——这就是我想要回答的。
      猜你喜欢
      • 2020-09-21
      • 2023-03-27
      • 2014-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-03
      • 1970-01-01
      相关资源
      最近更新 更多