Haskell 中的 Scala 偏函数
Scala's Partial Functions in Haskell
Scala 对部分函数有很好的支持,主要是因为在 Scala 中,当你定义一个部分函数时,它也为它定义了一个 isDefinedAt
函数。 Scala 还具有 orElse
和 andThen
函数来处理部分函数。
Haskell 确实通过简单地非详尽地定义一个函数来支持部分函数(尽管在 Haskell 社区中强烈反对它们)。但是通常要定义 isDefinedAt
函数,您必须使用某种异常处理,我无法弄清楚。一旦定义了 isDefinedAt
函数,就可以使用它来定义 orElse
并且 andThen
函数已经存在,因为 (.)
.
总之,我想定义一个函数,
isDefinedAt :: (a -> b) -> a -> Bool
isDefinedAt f x = -- returns True if f is defined at x else False
谁能告诉我这样的函数是怎么写的。
注意,我可以定义一个带签名的函数
isDefinedAt :: (a -> b) -> a -> IO Bool
通用 b
。但是我想要一个没有 IO 的函数在同域中。
关于 Scala 的偏函数的一篇不错的文章是 - How to create and use partial functions in Scala By Alvin Alexander
我建议您像在 Scala 中一样,为部分函数使用单独的类型。
import Control.Arrow
import Data.Maybe
type Partial = Kleisli Maybe
isDefinedAt :: Partial a b -> a -> Bool
isDefinedAt f x = isJust $ runKleisli f x
-- laziness should save some of the work, if possible
orElse :: Partial a b -> Partial a b -> Partial a b
orElse = (<+>)
andThen :: Partial a b -> Partial b c -> Partial a c
andThen = (>>>)
您的 isDefinedAt
版本不是 Scala 所做的(即使在签名中);当 isDefinedAt
为真时,PartialFunction
很有可能(尽管不鼓励)抛出异常。或者,当您明确定义一个(不使用文字)时,反之亦然:当 isDefinedAt
为假时 apply
不必抛出,用户有责任不调用它。所以直接等价物就是
data PartialFunction a b = PartialFunction { apply :: a -> b, isDefinedAt :: a -> Boolean }
这不是特别有用。
apply
和 isDefinedAt
仅在 Scala 中真正链接到需要编译器支持的 PartialFunction
文字:
我相信,您可以使用模板 Haskell 来模拟这一点,但老实说,我认为使用 Daniel Wagner 的回答中描述的更像 Haskell 的方法更好。正如他提到的,懒惰有帮助。
不过如果你确保 runKleisli f x
只执行一次,效果会更好;优化同时具有 isDefinedAt
和 runKleisli
的情况需要公共子表达式消除,编译器对此持谨慎态度,请参阅
你可以这样做(免责声明:我没有检查过相关类型类的法律,以及构造函数中是否存在字符串用于 Alternative
让我怀疑它是否合法)。 Scala的异构andThen
被fmap
覆盖;它的同质 andThen
/ compose
被 Category
的 >>>
/ <<<
覆盖; orElse
被 <|>
覆盖; lift
是 runToMaybe
.
但是,如果没有像 Scala 中存在的深度编译器集成,模式不完整性警告将与此交互很差。 Haskell 只有这些东西的模块级 pragma,你不会想在你声明无穷无尽的函数的任何模块中不加选择地关闭它们,否则你可能会得到令人讨厌的惊喜。根据您的用例,您可能会发现光学更加惯用且问题更少;您可以通过模板 Haskell.
为您生成样板文件
(注意:我称它为 Inexhaustive
因为 PartialFunction
有点用词不当,因为它暗示 Function
是总的. 但是 Scala 没有终止或正性检查器,所以编译器实际上无法谈论整体性;所以你会遇到这种奇怪的情况,其中不是部分函数的函数只是一个“常规”Function
,而你应该可以称它为“总Function
”。这里的问题不是部分或全部,这是一个更广泛的概念,而是模式匹配的无穷无尽。)
{-# LANGUAGE TypeApplications #-}
module Inexhaustive
( Inexhaustive, inexhaustive
, runToMaybe, isDefinedAt
) where
import Prelude hiding ((.), id)
import Control.Applicative
import Control.Exception
import Control.Category
import Data.Maybe
import System.IO.Unsafe (unsafePerformIO)
newtype Inexhaustive a b = Inexhaustive (a -> b)
inexhaustive :: (a -> b) -> Inexhaustive a b
inexhaustive = Inexhaustive
runToMaybe :: Inexhaustive a b -> a -> Maybe b
runToMaybe (Inexhaustive f) x =
let io = fmap Just $ evaluate $ f x
in unsafePerformIO $ catch @PatternMatchFail io (\_ -> return Nothing)
isDefinedAt :: Inexhaustive a b -> a -> Bool
isDefinedAt f = isJust . runToMaybe f
instance Functor (Inexhaustive z) where
fmap f (Inexhaustive g) = inexhaustive (f . g)
instance Applicative (Inexhaustive z) where
pure x = inexhaustive (const x)
(Inexhaustive zab) <*> (Inexhaustive za) = Inexhaustive (\z -> zab z $ za z)
instance Alternative (Inexhaustive z) where
empty = inexhaustive (\_ -> throw $ PatternMatchFail "inexhaustive empty")
f <|> g =
inexhaustive $ \x ->
case runToMaybe f x <|> runToMaybe g x of
Just y -> y
instance Category Inexhaustive where
id = inexhaustive id
(Inexhaustive f) . (Inexhaustive g) = Inexhaustive (f . g)
Scala 对部分函数有很好的支持,主要是因为在 Scala 中,当你定义一个部分函数时,它也为它定义了一个 isDefinedAt
函数。 Scala 还具有 orElse
和 andThen
函数来处理部分函数。
Haskell 确实通过简单地非详尽地定义一个函数来支持部分函数(尽管在 Haskell 社区中强烈反对它们)。但是通常要定义 isDefinedAt
函数,您必须使用某种异常处理,我无法弄清楚。一旦定义了 isDefinedAt
函数,就可以使用它来定义 orElse
并且 andThen
函数已经存在,因为 (.)
.
总之,我想定义一个函数,
isDefinedAt :: (a -> b) -> a -> Bool
isDefinedAt f x = -- returns True if f is defined at x else False
谁能告诉我这样的函数是怎么写的。
注意,我可以定义一个带签名的函数
isDefinedAt :: (a -> b) -> a -> IO Bool
通用 b
。但是我想要一个没有 IO 的函数在同域中。
关于 Scala 的偏函数的一篇不错的文章是 - How to create and use partial functions in Scala By Alvin Alexander
我建议您像在 Scala 中一样,为部分函数使用单独的类型。
import Control.Arrow
import Data.Maybe
type Partial = Kleisli Maybe
isDefinedAt :: Partial a b -> a -> Bool
isDefinedAt f x = isJust $ runKleisli f x
-- laziness should save some of the work, if possible
orElse :: Partial a b -> Partial a b -> Partial a b
orElse = (<+>)
andThen :: Partial a b -> Partial b c -> Partial a c
andThen = (>>>)
您的 isDefinedAt
版本不是 Scala 所做的(即使在签名中);当 isDefinedAt
为真时,PartialFunction
很有可能(尽管不鼓励)抛出异常。或者,当您明确定义一个(不使用文字)时,反之亦然:当 isDefinedAt
为假时 apply
不必抛出,用户有责任不调用它。所以直接等价物就是
data PartialFunction a b = PartialFunction { apply :: a -> b, isDefinedAt :: a -> Boolean }
这不是特别有用。
apply
和 isDefinedAt
仅在 Scala 中真正链接到需要编译器支持的 PartialFunction
文字:
我相信,您可以使用模板 Haskell 来模拟这一点,但老实说,我认为使用 Daniel Wagner 的回答中描述的更像 Haskell 的方法更好。正如他提到的,懒惰有帮助。
不过如果你确保 runKleisli f x
只执行一次,效果会更好;优化同时具有 isDefinedAt
和 runKleisli
的情况需要公共子表达式消除,编译器对此持谨慎态度,请参阅
你可以这样做(免责声明:我没有检查过相关类型类的法律,以及构造函数中是否存在字符串用于 Alternative
让我怀疑它是否合法)。 Scala的异构andThen
被fmap
覆盖;它的同质 andThen
/ compose
被 Category
的 >>>
/ <<<
覆盖; orElse
被 <|>
覆盖; lift
是 runToMaybe
.
但是,如果没有像 Scala 中存在的深度编译器集成,模式不完整性警告将与此交互很差。 Haskell 只有这些东西的模块级 pragma,你不会想在你声明无穷无尽的函数的任何模块中不加选择地关闭它们,否则你可能会得到令人讨厌的惊喜。根据您的用例,您可能会发现光学更加惯用且问题更少;您可以通过模板 Haskell.
为您生成样板文件(注意:我称它为 Inexhaustive
因为 PartialFunction
有点用词不当,因为它暗示 Function
是总的. 但是 Scala 没有终止或正性检查器,所以编译器实际上无法谈论整体性;所以你会遇到这种奇怪的情况,其中不是部分函数的函数只是一个“常规”Function
,而你应该可以称它为“总Function
”。这里的问题不是部分或全部,这是一个更广泛的概念,而是模式匹配的无穷无尽。)
{-# LANGUAGE TypeApplications #-}
module Inexhaustive
( Inexhaustive, inexhaustive
, runToMaybe, isDefinedAt
) where
import Prelude hiding ((.), id)
import Control.Applicative
import Control.Exception
import Control.Category
import Data.Maybe
import System.IO.Unsafe (unsafePerformIO)
newtype Inexhaustive a b = Inexhaustive (a -> b)
inexhaustive :: (a -> b) -> Inexhaustive a b
inexhaustive = Inexhaustive
runToMaybe :: Inexhaustive a b -> a -> Maybe b
runToMaybe (Inexhaustive f) x =
let io = fmap Just $ evaluate $ f x
in unsafePerformIO $ catch @PatternMatchFail io (\_ -> return Nothing)
isDefinedAt :: Inexhaustive a b -> a -> Bool
isDefinedAt f = isJust . runToMaybe f
instance Functor (Inexhaustive z) where
fmap f (Inexhaustive g) = inexhaustive (f . g)
instance Applicative (Inexhaustive z) where
pure x = inexhaustive (const x)
(Inexhaustive zab) <*> (Inexhaustive za) = Inexhaustive (\z -> zab z $ za z)
instance Alternative (Inexhaustive z) where
empty = inexhaustive (\_ -> throw $ PatternMatchFail "inexhaustive empty")
f <|> g =
inexhaustive $ \x ->
case runToMaybe f x <|> runToMaybe g x of
Just y -> y
instance Category Inexhaustive where
id = inexhaustive id
(Inexhaustive f) . (Inexhaustive g) = Inexhaustive (f . g)