【问题标题】:difficulty about passing function returning functor or monad type传递函数返回函子或单子类型的困难
【发布时间】:2018-09-09 16:00:06
【问题描述】:

我遇到一个案例,monad on return type 阻碍了高阶函数编程。

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)) 一样工作

【问题讨论】:

    标签: scala functional-programming monads functor


    【解决方案1】:

    您可以轻松实现您提出的语法(我将其称为 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 和g2,如下所示:

    println(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 本质上与Y 相同,这反过来意味着有某种方法可以将每个Option[Y] 转换为Y。但是没有从Option[Y] 到Y 的自然转换。这是一个相当普遍的现象:虽然有point/unit,而且从X 到M[X] 总是很容易进入monad,但通常没有安全/简单/无损的方式来获得M[X] 到X 的单子的em>out,例如:

    • 在Option[X] 上调用get 返回X,但可以抛出NoSuchElementException
    • 同样,在List 上调用head 会抛出异常,同时也会丢弃tail。
    • 等待Future 被阻塞
    • 从随机Distribution[X] 中采样X 会留下固定的X,但会删除所有其他可能的X 的概率信息

    等等。

    您可以使用Try 解决类型签名g(f: Int => Int) 的事实是因为Int => Int 部分并不十分精确:它不是身份单子,而是支持状态和异常的默认环境单子.在“现实”中,g 有点像g(f: Int => DefaultAmbientMonad[Int]),因为f 也可以抛出异常。

    现在,有趣的是,虽然没有从Option[X] 到X 的保证方法,但实际上有一种从Option[X] 到DefaultAmbientMonad[X] 的方法:如果Option 是None,只需抛出一些非常特别的NoneEmbeddingException。从DefaultAmbientMonad 到Option 又是不安全的:你可以捕捉到你的特殊NoneEmbeddingException,但是你必须“祈祷”不会抛出其他异常(这就是它“不安全”的原因)。

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

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

    这是我在上面的代码 sn-p 中实现的。

    但这几乎是您使用Try(...).toOption 所拥有的,只是您使用预定义的NoSuchElementException 而不是有些做作的NoneEmbeddingException!

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

    【讨论】:

    • 环境单子只是一个身份单子吗?
    • @WeiChingLin 这正是重点:不是!默认情况下,您的所有代码执行时都会产生有趣的副作用,会影响堆上的内存状态,并且可能会引发异常。这与它可能得到的纯函数组合身份单子相去甚远。这就是为什么你可以通过抛出异常然后捕获它们来将Option“嵌入”到这个环境非形式化的“monad”中。
    • 嗨@AndreyTyukin - 我试图理解你的解决方案,在我看来,即使函数 g 和 g2 需要一个函数 Int => Int 函数 f 可以返回一个只能被捕获的 None在运行时而不是在编译期间。这是正确的理解吗?另外,我理解的 X=>Y=>Z 确实是 Int => Option[Int] => Option[Int] 即使在隐式类中它表示为 Int => Int => Int,对吗?
    • @jjayadeep 构造使得f应该偶尔返回一个None,而getOrElse应该偶尔抛出一个异常.这是有意的。我们故意用模糊的Y 替换了返回类型Option[Y] 的显式声明,我们的意思是:它可以返回一个Y,但它可能也有副作用,或者它可能抛出异常。就是这样。我们将放弃显式的Option,然后在草率的环境单子中工作。正如我试图解释的那样,(X => Y) => Z 是“真正的”(X => AmbientMonad[Y]) => AmbientMonad[Z]),[... t.b.c]
    • @jjayadeep [...continued...] ... 除了 AmbientMonad 没有形式化,并且从未在函数签名中显示。 (X => Y) 部分应该被抛出。整个(X => Y) => Z 也应该 抛出异常。只是 AmbientMonad 从未明确写出,所以我们不得不求助于 (X => Y) => Z,而不是写 (X => AmbientMonad[Y]) => AmbientMonad[Z],它看起来与身份单子很相似,但实际上与身份单子相距甚远,因为它有例外和副作用。
    猜你喜欢
    • 1970-01-01
    • 2014-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-20
    • 1970-01-01
    • 2021-02-07
    • 1970-01-01
    相关资源
    最近更新 更多