2014-05-18 4 views
0

기능적 프로그래머 인 Erik Meijer는 재귀 대신에 fold를 사용해야 함을 알고 있습니다. 어떻게 접어서 사용하도록 다음과 같이 변환합니까? 나는 돌아 오는 길 한 가지를 볼 수는 있지만, fp에서는 회귀도 피해야합니다. 감사!재귀를 접기로 변환하는 방법

def tryOld(string: String, original: Exception, zomOldList: List[String => Double]): Double = { 
    zomOldList match { 
    case Nil => 
     throw original 
    case head :: tail => 
     try { 
     head(string) 
     } catch { 
     case ex: Exception => 
      tryOld(string, original, tail) 
     } 
    } 
} 
+3

정확하게 재귀에는 어떤 문제가 있습니까? 재귀는 재귀 적 문제를 해결하는 데 유용합니다. –

+0

재귀는 다른 관점에서 솔루션을 보는 것만으로 작동합니다. 나는 예외를 던져 버리는 기능을 가진 것은 아마 나쁜 습관이라고 포스트 아래에 답했다. Try with Double [Double]을 사용하면 효과가 있습니다. – ferk86

답변

0

다음은 foldLeft가있는 해결책입니다. tryOldString에 의해 호출 된 제네릭 함수를 처음 작성한 이후로 길다.

def tryOld[In, Error, Out](
    in: In, 
    original: Error, 
    zomOldList: List[In => Either[Error, Out]] 
): Either[Error, Out] = { 
    val seed: Either[Error, Out] = Left(original) 
    zomOldList.foldLeft(seed) { 
     case (prev, f) => 
     // stores first match without return 
     if (seed != prev) { 
      prev 
     } else { 
      f(in).fold(
      fa => 
       prev, 
      fb => 
       Right(fb) 
     ) 
     } 
    } 
    } 

    def tryOutString(string: String, original: Exception, zomOldList: List[String => Double]): Double = { 
    val zomFunctions: List[String => Either[Exception, Double]] = zomOldList.map { 
     f => 
     s: String => 
      try { 
      Right(f(s)) 
      } catch { 
      case e: Exception => 
       Left(e) 
      } 
    } 
    tryOld(string, original, zomFunctions).fold(
     bad => throw original, 
     good => good 
    ) 
    } 
2

접기로 구현할 수 없습니다. 컬렉션의 모든 요소에 대해 반복적으로 반복하는 반면 tryOld은 가끔 일찍 종료됩니다. 당신은 Stream의 게으름을 활용하고 collectFirstTry의 관점에서 구현할 수 :

import scala.util.Try 

def tryOld(string: String, original: Exception, zomOldList: List[String => Double]): Double = 
    zomOldList.toStream.map(x => Try(x(string))) collectFirst { 
    case Success(x) => x 
    } getOrElse (throw original) 

를하지만, 원래 재귀 구현은 명확하고 더 성능이 좋은 것입니다.

편집 : 스칼라는 하스켈의 foldr 같은 게으름의 특성을 가진 foldRight이 있다면

, 다음이 foldRight의 관점에서 정의 할 수 있습니다 : 그러나

implicit class GiveStreamAWorkingFoldRight[A](val s: Stream[A]) extends AnyVal { 
    def lazyFoldRight[B](z: => B)(f: (A,() => B) => B): B = 
    if (s.isEmpty) z else f(s.head,() => s.tail.lazyFoldRight(z)(f)) 
} 

def tryOld(string: String, original: Exception, zomOldList: List[String => Double]): Double = 
    zomOldList.toStream.lazyFoldRight(throw original) { (a, b:() => Double) => 
    try { 
     a(string) 
    } catch { 
     case ex: Exception => b() 
    } 
    } 

, 진정한 꼬리의 스칼라의 부족 -call 최적화는 b을 호출 할 때마다 스택 오버플로를 유발할 수있는 새로운 스택 프레임을 도입한다는 것을 의미합니다.

+0

Scalaz를 사용하고 있다면'lazyFoldRight'가'FoldableSyntax'에'foldr'로 이미 존재한다고 생각합니다. – Hugh

3

당신은 foldRight 함수 값 인 활용과이를 구현할 수 있습니다

import util.control.NonFatal 
def tryOld(string: String, original: Exception, zomOldList: List[String ⇒ Double]): Double = { 
    val unhandled: String ⇒ Double = _ ⇒ throw original 
    zomOldList.foldRight(unhandled) { (f, z) ⇒ 
    x ⇒ try { f(x) } catch { case NonFatal(_) ⇒ z(x) } 
    }(string) 
} 

주 우리는 우리가 잡기해서는 안 예외를 잡기 피하기 위해 여기 NonFatal를 사용합니다. 예외를 직접 사용하지 않아보다 우아한 방법으로이 코드를 작성할 수 있습니다.

+0

이것은 매우 영리하며, 나는이 해결책을 전혀 예상하지 못했습니다. 그것은 내 것보다 훨씬 깨끗합니다. 그러나 스칼라가 진정한 꼬리 호출 최적화를하지 못한다는 것은'z'를 호출 할 때마다 새로운 스택 프레임이 생겨 잠재적으로 스택 오버 플로우가 발생한다는 것을 의미합니다. 내 대답은 똑같은 문제로 고통 받고있다. – wingedsubmariner

+0

예. 내일 직장에있을 때 더 나은 해결책을 찾아 볼 것입니다. function throw exception을 가진 – Hugh

+0

은 아마도 나쁜 습관 일 것입니다. Try with Double [Double]을 사용하면 효과가 있습니다. – ferk86

관련 문제