Haskell 中的 Scala 偏函数

Scala's Partial Functions in Haskell

Scala 对部分函数有很好的支持,主要是因为在 Scala 中,当你定义一个部分函数时,它也为它定义了一个 isDefinedAt 函数。 Scala 还具有 orElseandThen 函数来处理部分函数。

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 }

这不是特别有用。

applyisDefinedAt 仅在 Scala 中真正链接到需要编译器支持的 PartialFunction 文字:

A PartialFunction's value receives an additional isDefinedAt member, which is derived from the pattern match in the function literal, with each case's body being replaced by true, and an added default (if none was given) that evaluates to false.

我相信,您可以使用模板 Haskell 来模拟这一点,但老实说,我认为使用 Daniel Wagner 的回答中描述的更像 Haskell 的方法更好。正如他提到的,懒惰有帮助。

不过如果你确保 runKleisli f x 只执行一次,效果会更好;优化同时具有 isDefinedAtrunKleisli 的情况需要公共子表达式消除,编译器对此持谨慎态度,请参阅

你可以这样做(免责声明:我没有检查过相关类型类的法律,以及构造函数中是否存在字符串用于 Alternative 让我怀疑它是否合法)。 Scala的异构andThenfmap覆盖;它的同质 andThen / composeCategory>>> / <<< 覆盖; orElse<|> 覆盖; liftrunToMaybe.

但是,如果没有像 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)