【问题标题】:Scala's Partial Functions in HaskellScala 在 Haskell 中的部分函数
【发布时间】:2018-06-13 20:50:16
【问题描述】:

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

Haskell 确实通过简单地非详尽地定义一个函数来支持部分函数(尽管在 Haskell 社区中强烈反对它们)。但是要定义isDefinedAt 函数,你必须使用某种异常处理,我无法弄清楚。一旦定义了isDefinedAt 函数,就可以使用它来定义orElseandThen 函数已经存在为(.)

总之,我要定义一个函数,

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

【问题讨论】:

  • “定义”是指“不是undefined”吗?为什么不直接使用Maybe
  • 根据设计,如果不使用 IO,就无法在 Haskell 中捕获异常。正如您所说,强烈建议不要使用偏函数;如果你编写一个部分函数,​​你应该考虑你的责任,以确保它永远不会在其域之外被调用。如果不能预先保证,您应该明确说明该函数可以通过合适的结果类型失败。
  • @leftaroundabout 我知道并完全理解你在说什么,但这种对部分函数的消极态度对我来说似乎有点教条。使用偏函数有充分的理由,例如头函数和尾函数。并且在许多此类情况下在共同域中使用 Maybe 似乎并不诚实,因为当有人想到 head 函数时,他/她认为它是一个未在空列表中定义的函数,所以我确实希望我的程序在我崩溃时崩溃要求她计算“head []”,错误提示它没有定义,最好是。

标签: scala haskell partial-functions


【解决方案1】:

我建议您像在 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 = (>>>)

【讨论】:

  • 感谢丹尼尔的建议。我接受你的回答,因为它对我来说已经足够好了,但我在想是否有办法将非详尽定义的函数(比如说,我已经在代码中并且不想更改)转换为部分。我似乎找不到一个。如果没有办法,那是什么原因呢。
【解决方案2】:

您的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.

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

如果你确保runKleisli f x 只执行一次,效果会更好;优化同时拥有isDefinedAtrunKleisli 的情况需要消除公共子表达式,编译器对此非常谨慎,请参阅Under what circumstances could Common Subexpression Elimination affect the laziness of a Haskell program?

【讨论】:

  • 我认为 OP 对 PartialFunction type 而不是 PartialFunction literal 的属性更感兴趣。
  • PartialFunction类型的一个重要属性isDefinedAt的结果值与apply是否会抛出异常无关。
【解决方案3】:

你可以做这样的事情(免责声明:我没有检查相关类型类的规则,并且Alternative中的异常的构造函数中存在一个字符串让我想知道是否这是合法的)。 Scala 的异构andThenfmap 覆盖;它的同质andThen / compose&gt;&gt;&gt; / &lt;&lt;&lt; 覆盖,来自CategoryorElse&lt;|&gt; 覆盖; liftrunToMaybe

但是,如果没有像 Scala 那样的深度编译器集成,模式不完整性警告将与此交互不良。 Haskell 对这些东西只有模块级的编译指示,你不会想在任何你声明不穷尽的函数的模块中不加选择地关闭它们,否则你可能会得到令人讨厌的惊喜。根据您的用例,您可能会发现光学更惯用且问题更少;您可以通过 Template Haskell 为您生成样板。

(注意:我称它为 Inexhaustive 是因为 PartialFunction 用词不当,因为它暗示 Function 是完全的。但是 Scala 没有终止或积极性检查器,所以编译器实际上不能谈论整体性;所以你会遇到这种奇怪的情况,一个不是部分函数的函数只是一个“常规”Function,而你应该可以称它为“total 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)

【讨论】:

  • 您可能还喜欢spoon
猜你喜欢
  • 1970-01-01
  • 2011-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多