Haskell 中的合并运算符替代项

Coalesce operator alternative in Haskell

我收到 2 个字符串路径作为参数:inputoutput 我想从 input 路径读取文件并将其写入 output小路。 我想处理关于输入/输出的所有 4 种情况 paths.When 其中一个是 null 我想给它一个默认值 value.Is 有什么像合并运算符?我不想重写 do 子句所有场景:

场景

 func   null _  -> {do clause}
        _ null  -> {do clause}
        _  _   ->  {do clause}
        x  y   ->  {do clause}

let defaultInPath="inPath.txt"
    defaultOutPath="outPath.txt"

我想实现的 -do 子句:

  do 
    text<-readFile input??defaultIn
    writeFile  output??defaultOut text
    return text 

P.S 我是 Haskell 的新手,我真的很想掌握它。

使用Maybe类型构造器

首先,使用 Maybe 正确编码您的 "null" 字符串。然后,如果参数是 Nothing.

,则使用 maybe 函数 return 您的默认值
func :: Maybe String -> Maybe String -> IO String
func inFile outFile = do
   text <- readFile $ maybe defaultIn id inFile
   writeFile (maybe defaultOut id outFile) text
   return text

使用Data.Maybe

如果您不介意额外导入,可以使用 fromMaybe d = maybe d id

import Data.Maybe

func :: Maybe String -> Maybe String -> IO String
func inFile outFile = do
   text <- readFile $ fromMaybe defaultIn inFile
   writeFile (fromMaybe defaultOut outFile) text
   return text

定义??你自己

无论哪种方式,您都可以从任一函数定义自己的合并运算符:

?? :: Maybe String -> String -> String
(??) = flip fromMaybe
-- a ?? b = fromMaybe b a
-- a ?? b = maybe b id a

并写

func inFile outFile = do
    text <- readFile (inFile ?? defaultIn)
    writeFile (outFile ?? defaultOut) text
    return text

使用Maybe

你的四种类型的电话看起来像这样,假设你还没有 从 return 是 Maybe String 值的函数获取值。

func Nothing Nothing
func (Just "input.txt") Nothing
func Nothing (Just "output.txt")
func (Just "input.txt") (Just "output.txt")

如果您有一个可能提供或不提供的值,您绝对应该使用 Maybe 安全灵活地对其进行编码。

然而,如果你真的想替换空字符串或任何其他魔法值,你可以轻松地使用 if..then..else 作为表达式:

func :: String -> IO ()
func input = do 
  text <- readFile (if input == "" then defaultIn else input) 
  putStrLn text

当然,一旦你切换到 Maybe 并发现自己有一个纯字符串,你可以使用相同的方式调用它:

func :: Maybe String -> IO ()
func input = do
  text <- readFile $ fromMaybe "default.txt" input 
  putStrLn text

main = do
  putStrLn "Enter filename or blank for default:"
  file <- getLine
  func (if file == "" then Nothing else Just file)

此处已有的其他答案比以下内容更实用,但如果您对事物的更概念化的观点感兴趣,请继续阅读。

首先,Haskell 没有空引用,但如果要对缺失值建模,可以使用 Maybe。例如,如果你想把空字符串当作缺失值,你可以这样写一个转换函数:

maybeFromNull :: Foldable t => t a -> Maybe (t a)
maybeFromNull xs = if null xs then Nothing else Just xs

你这样使用它:

*Q49616294> maybeFromNull "foo"
Just "foo"
*Q49616294> maybeFromNull ""
Nothing

当谈论 Haskell 中的空合并运算符时,这很有趣的原因是 there's a monoid over Maybe that corresponds to that。它称为 First,它 return 是一系列候选值中最左边的非 Nothing 值。

出于稍后可能更清楚的原因,我将使用 Data.Semigroup 中的那个,所以

import Data.Semigroup

为了获得 Monoid 优于 Maybe 的行为,您需要将 First 值包装在 Option 中;例如:

*Q49616294> (Option $ Just $ First 42) <> (Option $ Just $ First 1337)
Option {getOption = Just (First {getFirst = 42})}

当然,这是 select 最左边值的一种相当冗长的方法,但强调空合并是 'just' 一个幺半群:

*Q49616294> (Option $ Just $ First 42) <> (Option Nothing)
Option {getOption = Just (First {getFirst = 42})}
*Q49616294> (Option Nothing) <> (Option $ Just $ First 1337)
Option {getOption = Just (First {getFirst = 1337})}

由于这对于实际使用而言过于冗长,您可以决定编写一个自定义运算符,将 Maybe 值重新打包为 Option First 值,应用 <> 操作,然后解包结果从 Option First 回到 Maybe:

(<?>) :: Maybe a -> Maybe a -> Maybe a
mx <?> my =
  let ofx = Option $ sequenceA $ First mx
      ofy = Option $ sequenceA $ First my
      leftmost = ofx <> ofy
  in getFirst $ sequenceA $ getOption $ leftmost

虽然您可以将此运算符写成一个大表达式,但我选择使用 let...in 语法来 'show my work'。

不过还有一个问题:

*Q49616294> Just 42 <?> Just 1337
Just 42
*Q49616294> Nothing <?> Nothing
Nothing

虽然操作 return 是一个 Just 值,但只要至少一个参数是 Just 值,它 可以 return Nothing.

如何应用回退值以确保在所有情况下都能获得值?

您可以利用 Option 成为 Foldable 的优势,然后仍然折叠 <> - 只是这一次,正在使用不同的 Monoid 实例:

(<!>) :: Maybe a -> a -> a
mx <!> y =
  let ofx = Option $ sequenceA $ First mx
      fy  = First y
  in getFirst $ foldr (<>) fy ofx

此运算符折叠 ofx,使用 fy 作为初始值。这里,<>属于FirstSemigroup,无条件return是最左边的值。这里没有涉及 Option,因为 foldr 剥离了该层。但是,由于我们从右侧折叠,如果 ofx 包含一个值,则初始值 fy 将始终被忽略。

*Q49616294> Just 42 <!> 1337
42
*Q49616294> Nothing <!> 1337
1337

您现在可以按如下方式编写所需的函数:

copyFile :: String -> String -> IO String
copyFile input output = do
  text <- readFile $ (maybeFromNull input) <!> defaultInPath
  writeFile (maybeFromNull output <!> defaultOutPath) text
  return text 

事实证明,在这种情况下,您甚至不需要 <?>,但在其他情况下,您可以使用此运算符链接任意数量的潜在值:

*Q49616294> Just 42 <?> Nothing <?> Just 1337 <!> 123
42
*Q49616294> Nothing <?> Nothing <?> Just 1337 <!> 123
1337
*Q49616294> Nothing <?> Nothing <?> Nothing <!> 123
123

这种实现空合并行为的方式不仅不必要地复杂,而且如果它表现不佳会雪上加霜,我也不会感到惊讶。

不过,它确实说明了 Haskell 内置抽象的强大功能和表现力。