Haskell 中的合并运算符替代项
Coalesce operator alternative in Haskell
我收到 2 个字符串路径作为参数:input
和 output
我想从 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
作为初始值。这里,<>
属于First
Semigroup
,无条件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 内置抽象的强大功能和表现力。
我收到 2 个字符串路径作为参数:input
和 output
我想从 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
作为初始值。这里,<>
属于First
Semigroup
,无条件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 内置抽象的强大功能和表现力。