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 的偏函数的好文章是 - 如何在 Scala 中创建和使用分部函数 作者: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
很有可能(尽管不鼓励(抛出异常。或者,当你显式定义一个(不使用文字(时,反之亦然:apply
不必在isDefinedAt
为 false 时抛出,那么用户有责任不调用它。所以直接等价物只是
data PartialFunction a b = PartialFunction { apply :: a -> b, isDefinedAt :: a -> Boolean }
这不是特别有用。
apply
和isDefinedAt
仅在 Scala 中真正链接PartialFunction
需要编译器支持的文字:
Partial 函数的值接收一个额外的 isDefinedAt 成员,该成员派生自函数文本中的模式匹配,每个事例的主体都替换为 true,并添加一个计算结果为 false 的默认值(如果未给出(。
我相信你可以通过使用模板哈斯克尔来模拟这一点,但老实说,我认为使用丹尼尔·瓦格纳的答案中描述的更像哈斯克尔的方法更好。正如他所提到的,懒惰会有所帮助。
虽然如果你确保runKleisli f x
只执行一次,效果会更好;优化同时具有isDefinedAt
和runKleisli
的情况需要公共子表达式消除,并且编译器对此持谨慎态度,请参阅在什么情况下公共子表达式消除会影响Haskell程序的懒惰性?
你可以做这样的事情(免责声明:我没有检查相关类型类的规律,并且在构造函数中存在一个字符串来表示Alternative
中的异常让我怀疑它是否合法(。Scala的异构andThen
被fmap
覆盖;其均匀的andThen
/compose
被Category
的>>>
/<<<
所覆盖;orElse
由<|>
覆盖;lift
runToMaybe
.
但是,如果没有像 Scala 中那样的深度编译器集成,模式不完整警告将与此交互不良。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)