在Haskell中合并运算符替代

时间:2018-04-02 18:01:33

标签: haskell coalesce

我收到2个字符串路径作为参数:inputoutput我希望从input路径读取文件并将其写入{{1路径。 我想要处理有关输入/输出路径的所有4个场景。当其中一个为null时,我想给它一个默认值。是否有类似于coalesce运算符的东西?我不想为所有场景重写do子句: / p>

方案

output

我想达到什么-do子句:

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

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

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

3 个答案:

答案 0 :(得分:2)

使用Maybe类型构造函数

首先,编码你的&#34; null&#34;使用maybe正确填充字符串。然后,如果参数为Nothing,则使用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

使用Maybe String

假设您还没有,那么您的四种类型的电话会是这样的 从返回func Nothing Nothing func (Just "input.txt") Nothing func Nothing (Just "output.txt") func (Just "input.txt") (Just "output.txt") 值的函数中获取值。

{{1}}

答案 1 :(得分:2)

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

首先,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,它从一系列候选者中返回最左边的非Nothing值。

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

import Data.Semigroup

为了使Monoid行为超过Maybe,您需要将First值包含在Option中; e.g:

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

当然,选择最左边的值是一种啰嗦的方式,但突出显示null coalesce'只是'幺半群:

*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语法来“显示我的工作”。

但仍存在一个问题:

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

虽然只要至少有一个参数是Just值,操作就会返回Just值,但可以返回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。此处,<>属于First Semigroup,它无条件地返回最左边的值。此处不涉及Option,因为foldr剥离了该层。但是,由于我们从右侧折叠,如果fy包含值,则始终会忽略初始值ofx

*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内置抽象的力量和表现力。

答案 2 :(得分:1)

如果您有可能提供或未提供的值,您应该使用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)