2010-05-10 2 views
4

이것은 반드시 스칼라 질문 일 필요는 없지만 변경 가능한 상태, 기능적 사고 및 그 종류를 피하는 것과 관련된 디자인 질문입니다. Scala를 사용하고 있습니다. 요구 사항이 주어지면변경 가능한 함수 알고리즘의 기능적 발견

:

  • 입력 중 하나를 성공하거나 FAIL 1 10

  • 최종 출력 사이의 임의의 숫자의 본질적으로 무한한 스트림에서 오는

  • 이 수 특정 시간에 스트림에 '듣기'하는 여러 개체가있을 수 있으며 서로 다른 시간에 청취를 시작할 수 있으므로 모두 '첫 번째'숫자와 다른 개념을 가질 수 있습니다. 따라서 스트림에 대한 리스너는 스트림 자체에서 분리되어야합니다.

의사 코드 : 여기

if (first number == 1) SUCCEED 
else if (first number >= 9) FAIL 
else { 
    first = first number 
    rest = rest of stream 
    for each (n in rest) { 
    if (n == 1) FAIL 
    else if (n == first) SUCCEED 
    else continue 
    } 
} 

는 가능한 가변 구현 :

sealed trait Result 
case object Fail extends Result 
case object Succeed extends Result 
case object NoResult extends Result 

class StreamListener { 
    private var target: Option[Int] = None 

    def evaluate(n: Int): Result = target match { 
    case None => 
     if (n == 1) Succeed 
     else if (n >= 9) Fail 
     else { 
     target = Some(n) 
     NoResult 
     } 

    case Some(t) => 
     if (n == t) Succeed 
     else if (n == 1) Fail 
     else NoResult 
    } 
} 

이 작동하지만 나에게 냄새가 난다. StreamListener.evaluate는 참조가 투명하지 않습니다. 그리고 NoResult 토큰의 사용은 옳다고 생각하지 않습니다. 그것은 명확하고 사용하기 쉽고/코드가 있지만 장점이 있습니다. 게다가 거기에는 기능적 해결책이 있어야합니다.

나는이 다른 가능한 옵션을 마련했습니다

  • (아마도 새) StreamListener을 반환 평가하는 데, 그러나 이것은 내가 '아무튼 StreamListener의 하위 유형을 결과 할 것이다 의미 기분 나빠.

  • 평가할 때 Stream [Int]을 매개 변수로 사용하고 StreamListener가 실패 또는 성공을 결정하는 데 필요한만큼 Stream을 소비하도록 할 수 있습니다. 이 접근법에서 볼 수있는 문제는 리스너를 등록하는 클래스가 각 숫자가 생성 된 후에 각 리스너를 쿼리하고 실패 나 성공시 즉시 적절한 조치를 취해야한다는 것입니다. 이 방법을 사용하면 각 청취자가 평가를 완료 할 때까지 스트림 평가를 강제로 수행하기 때문에 이러한 현상이 발생할 수있는 방법을 알 수 없습니다. 단일 숫자 생성에 대한 개념은 없습니다.

표준 스칼라/FP 관용구가 있습니까?

답변

4

첫 번째 가능한 옵션을 고려해 볼 때 나는 왜 Result에 관련한 특정의 서브 타입을 StreamListener로하는 것이 아니라, Result를 StreamListener의 서브 타입으로합니다.

sealed trait Result 
sealed trait FinalizedResult extends Result 

trait StreamListener { 
    def evaluate(n: Int): Result 
} 

case object Uninitialized extends Result with StreamListener { 
    def evaluate(n: Int): Result = { 
    n match { 
     case i if (n == 1) => Succeed 
     case i if (n >= 9) => Fail 
     case _ => Initialized(n) 
    } 
    } 
} 

case class Initialized(target: Int) extends Result with StreamListener { 
    def evaluate(n: Int): Result = { 
    n match { 
     case i if (n == target) => Succeed 
     case i if (n == 1) => Fail 
     case _ => this 
    } 
    } 
} 

case object Succeed extends FinalizedResult 
case object Fail extends FinalizedResult 

그렇다면 Results/StreamListener에 대한 참조를 추적하기 위해 호출 코드까지 변경 가능성을 섞어 보지 않으시겠습니까?

+0

감사합니다. 나는이 해결책을 많이 좋아한다. mutability를 호출 코드까지 이동시키는 것은 무엇을 의미합니까? 호출 코드가 StreamListeners의 목록을 유지하고 각 호출에 대해 evaluate 호출 결과를 매핑 한 다음 결과 목록을 (FinalizedResults, EverythingElse)로 분할하고 성공/실패를 처리 한 다음 그 자체로 새로운 상태를 만듭니다 미해결의 청취자 + 새롭게 등록 된 청취자. 이 접근 방법에 문제가 있는지 잠시 확인해야 할 것입니다. –

+0

저는 전화 코드를 아주 멀리 생각하지 않았습니다. 네가 좋은 접근 방법 인 것처럼 들리네. –

2

저는 스칼라에 대해 잘 모릅니다. 하스켈리스트는 게으르며 '스트림'을 나타낼 수 있으며 '필요에 따라 호출'은 목록/스트림이 필요한만큼만 평가되도록합니다 (각 셀은 한 번만).

F #의 경우 PowerPack에는 동일한 방식으로 동작하는 LazyList 모듈이 있습니다. 즉, 값의 무한한 흐름을 정의하고 머리/나머지 목록처럼 패턴 일치를 수행하고 '필요한만큼만'평가할 수 있으며 다른 소비자가 다른 위치에서 서로 다른 '포인터'를 가질 수 있습니다 (예 : 변경 불가능한 머리/나머지 목록에서는 평가의 '경계'에서 가장 진보 된 '포인터'에 대해 평가 효과 만 동기화됩니다.

그래서 나는 게으른 목록처럼 올바른 '데이터 구조'가 필요하다고 생각합니다. 나머지는 자연스럽게 빠져 나옵니다.(사이드 노트 : 스펙과 같은 기능이 무한 루프 (비 종결)에 걸릴 수 있습니다.

+0

맞습니다. 무제한 가능하지만, 난수 생성기가 실제로 균일하게 분포 된 숫자 스트림을 반환하지 않는 한 가능성은 없습니다. 다른 의미가 있습니까? –

관련 문제