我遇到一个案例,即返回类型的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))
一样工作
答案 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
传递给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]
到M[X]
进入monad总是很容易,但通常没有安全措施/简单/无损的方式从X
到get
获取 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]
也可以抛出异常。
现在,有趣的是,虽然没有保证从X
到Option[X]
的方法,但实际上是从{{1}获取的方法} DefaultAmbientMonad[X]
:如果NoneEmbeddingException
为Option
,则只抛出一些非常特殊的None
。从DefaultAmbientMonad
到Option
再次不安全:您可以抓住您的特殊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
,没关系。