简体   繁体   English

Haskell中的Coalesce运算符替代方案

[英]Coalesce operator alternative in Haskell

I am receiving 2 string path's as an argument: input and output and i want to read the file from input path and write it to the output path. 我收到2个字符串路径作为参数: inputoutput ,我想从input路径读取文件并将其写入output路径。 I want to treat all 4 scenarios regarding input / output paths.When one of them is null i want to give it a default value.Is there anything like a coalesce operator?I do not want to rewrite the do clause for all scenarios: 我想处理关于输入/输出路径的所有4种情况。当其中之一为null时,我想给它一个默认值。是否有像合并运算符之类的东西?我不想为所有情况重写do子句:

Scenarios 情境

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

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

What i want to achieve -do clause: 我想实现的-do子句:

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

PS I am new to Haskell and i am really trying to get a grasp of it. 附言:我是Haskell的新手,我真的在试图掌握它。

Use the Maybe type constructor 使用Maybe类型构造函数

First, encode your "null" strings correctly using Maybe . 首先,使用Maybe正确编码“空”字符串。 Then, use the maybe function to return your default values if an argument is Nothing . 然后,如果参数为Nothing ,则maybe使用maybe函数返回默认值。

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

Using Data.Maybe 使用Data.Maybe

If you don't mind an extra import, you can use fromMaybe d = maybe d id . 如果您不介意额外导入,则可以使用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

Defining ?? 定义?? yourself 你自己

Either way, you can define your own coalescing operator from either function: 不管哪种方式,您都可以从两个函数中定义自己的合并运算符:

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

and write 和写

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

Using Maybe 使用Maybe

Your four types of calls would look like this, assuming you aren't already getting the values from a function that returns a Maybe String value. 假设您尚未从返回Maybe String值的函数中获取值,则四种调用类型将如下所示。

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

The other answers already here are more practical than what follows, but if you're interested in a more conceptual view of things, then read on. 此处已经存在的其他答案比后面的答案更实际,但是如果您对事物的更概念性的看法感兴趣,请继续阅读。

First, Haskell doesn't have null references, but if you want to model missing values, you can use Maybe . 首先,Haskell没有空引用,但是如果要对缺失值建模,可以使用Maybe If, for example, you want to treat the empty string as a missing value, you can write a conversion function like this: 例如,如果要将空字符串视为缺失值,则可以编写如下转换函数:

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

You use it like this: 您可以这样使用它:

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

The reason that this is interesting when the talk falls on null coalescing operators in Haskell is that there's a monoid over Maybe that corresponds to that . 当讨论落在Haskell中的空合并运算符上时,这很有趣的原因是Maybe上有一个与之相对应的monoid It's called First , and it returns the leftmost non- Nothing value from a series of candidates. 它被称为First ,它从一系列候选中返回最左边的Nothing值。

For reasons that may be clearer later, I'll use the one from Data.Semigroup , so 出于稍后可能更清楚的原因,我将使用Data.Semigroup的一个,因此

import Data.Semigroup

In order to get the Monoid behaviour over Maybe , you need to wrap the First value in an Option ; 为了获得MaybeMonoid行为,您需要将First值包装在Option eg: 例如:

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

Granted, that's quite a long-winded way to select the left-most value, but highlights that null coalesce is 'just' a monoid: 当然,选择最左边的值是一个漫长的过程,但要强调的是,空合并是“仅”类半体动物:

*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})}

Since that's too verbose for practical use, you could decide to write a custom operator that repackages Maybe values as Option First values, applies the <> operations, and then unwraps the result from Option First back to Maybe : 由于对于实际使用而言太冗长,因此您可以决定编写一个自定义运算符,将Maybe值重新打包为Option First值,应用<>操作,然后将结果从Option FirstMaybe

(<?>) :: 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

While you could write this operator as one big expression, I chose to use the let...in syntax to 'show my work'. 虽然您可以将此运算符写为一个大表达式,但我选择使用let...in语法来“显示我的作品”。

One problem remains, though: 但是,仍然存在一个问题:

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

While the operation returns a Just value as long as at least one of the arguments is a Just value, it can return Nothing . Just该操作至少返回一个Just值,该操作就返回Just值,但是它可以返回Nothing

How do you apply a fallback value so that you're guaranteed to get a value in all cases? 如何应用后备值,以确保在所有情况下都能获得值?

You can take advantage of Option being Foldable , and then still fold over <> - only this time, a different Monoid instance is in use: 您可以利用OptionFoldable ,然后仍然将<>折叠起来-只是这次,使用了另一个Monoid实例:

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

This operator folds over ofx , using fy as an initial value. 该运算符使用fy作为初始值对ofx进行折叠。 Here, <> belongs to the First Semigroup , which unconditionally returns the leftmost value. 在这里, <>属于First Semigroup ,它无条件返回最左边的值。 There's no Option involved here, since foldr peels that layer away. 这里没有Option ,因为文件foldr剥掉该层。 Since we're folding from the right, though, the initial value fy will always be ignored if ofx contains a value. 但是,由于我们是从右折叠,因此,如果ofx包含一个值,则初始值fy将始终被忽略。

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

You can now write the desired function as follows: 现在,您可以编写所需的函数,如下所示:

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

It turns out that in this case, you don't even need <?> , but in other situations, you could use this operator to chain as many potential values as you'd like: 事实证明,在这种情况下,您甚至不需要<?> ,但是在其他情况下,您可以使用此运算符来链接任意数量的潜在值:

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

Not only is this way of implementing null coalescing behaviour needlessly complicated, I wouldn't be surprised if it doesn't perform well to add spite to injury. 这种实现null合并行为的方式不仅不必要地复杂,而且如果它不能很好地增加伤害,那么我也不会感到惊讶。

It does, however, illustrate the power and expressivity of Haskell's built-in abstractions. 但是,它确实说明了Haskell内置抽象的功能和表现力。

If you have a value that may or may not be provided, you should definitely encode this safely and flexibly with Maybe. 如果您提供或不提供的值,则绝对应该使用Maybe对其进行安全,灵活的编码。

However, if you really want to replace an empty string or any other magic value, you can easily use if..then..else as an expression: 但是,如果您确实要替换空字符串或任何其他魔术值,则可以轻松地将if..then..else用作表达式:

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

and of course, once you do switch to Maybe and find yourself with a plain string, you can use the same to call it: 当然,一旦您切换到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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM