关于传递函数返回函子或monad类型的困难

时间:2018-03-30 15:47:05

标签: scala functional-programming monads functor

我遇到一个案例,即返回类型的monad阻碍了高阶函数编程。

val fOpt: (x: Int) => Option[Int]

def g(f: Int=>Int): Int

如何致电g(fOpt)并将结果显示为Option[Int]? 我的解决方案是Try(g(fOpt(_).get)).toOption。但我不满意。

是否有我不知道的方法可以解决这个问题。我问这个是因为我对函数式编程知之甚少(这么多的模式和理论)。 我希望函数返回会有functor这样的东西,它可以像val ret:Option[Int] = fOpt.mapReturn(f=>g(f))一样工作

1 个答案:

答案 0 :(得分:3)

您可以轻松实现您提议的语法(我称之为toAmbient而不是mapReturn,之后我将f替换为h以分隔标识符更加明显。)

这是一个在函数上使用隐式包装类的实现:

implicit class UnsafeEmbedAmbientOps[X, Y](f: X => Option[Y]) {
  class NoneToAmbientEmbeddingException extends RuntimeException
  def toAmbient[Z](a: (X => Y) => Z): Option[Z] = {
    try {
      Some(a(f(_).getOrElse(throw new NoneToAmbientEmbeddingException)))
    } catch {
      case e: NoneToAmbientEmbeddingException => None
    }
  }
}

现在,您可以定义f: Int => Option[Int]g, g2以及Int => Int并返回Int的各种val f: Int => Option[Int] = x => Map(1 -> 1, 2 -> 4, 3 -> 9).get(x) def g(f: Int => Int): Int = f(1) + f(2) def g2(f: Int => Int): Int = f(1) + f(42)

f

然后将g传递给g2println(f.toAmbient(h => g(h))) println(f.toAmbient(h => g2(h))) ,如下所示:

Some(5)
None

这将打印:

Try(g(fOpt(_).get)).toOption

扩展评论

我想尝试解释为什么我发现f: X => Option[Y] 实际

假设有一些自然的方式来转换每个

fPrime: X => Y

进入

Unit => Option[Y]

这意味着有一种自然的方法可以将每个Unit => Y转换为Unit => Y。由于YOption[Y]基本相同,这反过来意味着有一些方法可以将每个Y转换为Option[Y]。但是Ypoint没有自然转变。这是一个相当普遍的现象:虽然有unit / X,但从M[X]M[X]进入monad总是很容易,但通常没有安全措施/简单/无损的方式从Xget获取 out monad,例如:

  • Option[X]上呼叫X会返回NoSuchElementException,但可以抛出head
  • 同样,在List上调用tail可能会抛出异常,也会抛弃Future
  • 等待X阻止
  • 从随机Distribution[X]中取样X会为您留下固定的X,但会删除有关所有其他可能g(f: Int => Int)的概率的信息

等。

您可以使用Try处理类型签名Int => Int,这是因为g部分并不是非常精确:它不是标识monad,而是默认环境monad支持状态和异常。在“现实”中,g(f: Int => DefaultAmbientMonad[Int])更像f,因为Option[X]也可以抛出异常。

现在,有趣的是,虽然没有保证从XOption[X]的方法,但实际上从{{1}获取的方法} DefaultAmbientMonad[X]:如果NoneEmbeddingExceptionOption,则只抛出一些非常特殊的None。从DefaultAmbientMonadOption再次不安全:您可以抓住您的特殊NoneEmbeddingException,但是您必须“祈祷”不会抛出其他异常(这就是为什么它“不安全” “)。

因此,将fOpt传递给g的最“系统”方式实际上是

class NoneEmbeddingException extends RuntimeException

try {
  Option(g(fOpt(_).getOrElse(throw new NoneEmbeddingException)))
} catch {
  case e: NoneEmbeddingException => None
}

这是我在上面的代码段中实现的。

但这几乎与Try(...).toOption已有的相同,只是您使用预定义的NoSuchElementException而不是有点人为的NoneEmbeddingException

所以,我只想说:你的解决方案是正确的,可以通过系统讨论从Option monad到默认环境monad的自然转换来证明这一点,并且它并不特别令人惊讶。我的个人意见:只需使用Try(...).toOption,没关系。