[英]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个字符串路径作为参数:
input
和output
,我想从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的新手,我真的在试图掌握它。
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
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
??
??
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
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
; 为了获得
Maybe
的Monoid
行为,您需要将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 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
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: 您可以利用
Option
是Foldable
,然后仍然将<>
折叠起来-只是这次,使用了另一个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.