2014-10-04 3 views
3

일부 로컬 상태를 사용하여 빌드하는 코드를 작성하려고합니다. 그렇게하지 있도록 makeThings 내부 s 상태 매개 변수가 암시 적 콜백 것을 만들 수있는 방법이 있는지 궁금 해요OCaml에서 암시 적 로컬 상태를 수행하는 관용적 인 방법이 있습니까?

type state = int ref 

let uniqueId : (state -> int) = 
fun s -> incr s; !s 

let makeThings : ((state -> 'a) -> 'a) = 
fun body -> body (ref 0) 

let() = 

    let x1 = makeThings(fun s -> 
    let i = uniqueId s in  (* 1 *) 
    i 
) in 
    print_int x1; print_newline(); (* Prints 1 *) 

    (* Each makeThings callback gets its own local state. 
    The ids start being generated from 1 again *) 
    let x2 = makeThings(fun s -> 
    let i = uniqueId s in  (* 1 *) 
    let j = uniqueId s in  (* 2 *) 
    i + j 
) in 
    print_int x2; print_newline(); (* Prints 3 *) 

() 

예를 들어, 순차적 인 정수를 생성하기 위해 로컬 상태를 사용하는 다음 코드를 살펴 반복해서 입력해야하므로 모든 uniqueId 호출에 동일한 상태 매개 변수가 전달되도록 보장해야합니다. 예를 들어, 하스켈에서 당신은 모나드를 사용할 수 있고 할 표기법을 OCaml의에서

makeThings $ do 
    i <- uniqueId 
    j <- uniqueId 
    return (i + j) 

의 라인, 내 마음에 와서 유일한 물건에 따라 코드로 결국 s하고 있습니다 전역 변수 (매우 바람직하지 않은) 또는 하스켈의 모나드 인터페이스를 에뮬레이트하려는 시도가 있습니다. 두려움은 많은 작업이 될 것이고 코드 표기가 부족하여 느린 코드 tahts로 끝납니다. 생각하지 못한 대안이 있습니까?

+0

에서 얻을 수 있습니다 전 세계적으로 국가의 변화를 원한다. 나는 당신이 달성하고자하는 것이라고 생각합니다 : '1. 상태를 갖는 함수 f (make_things). 2. f를 호출 할 때마다 상태는 으로 재설정됩니다. 3. 그러나 f의 내부 호출 중에는 상태가 자동으로 변경 될 수 있습니다. '내 추측이 맞습니까? –

답변

1

이미 가지고있는 것에 약간의 변형이 있습니다.대신 지속을 사용하는, 단지 신선한 상태를 생성하는 기능을 제공 :

module State : sig 

    type t 

    val fresh : unit -> t 

    val uniqueId : t -> int 

end = struct 

    type t = int ref 

    let fresh() = ref 0 

    let uniqueId s = incr s; !s 

end 

let() = 
    let x1 = 
    let s = State.fresh() in 
    let i = State.uniqueId s in 
     i 
    in 
    print_int x1; 
    print_newline() (* Prints 1 *) 

let() = 
    let x2 = 
    let s = State.fresh() in 
    let i = State.uniqueId s in  (* 1 *) 
    let j = State.uniqueId s in  (* 2 *) 
     i + j 
    in 
    print_int x2; 
    print_newline() (* Prints 3 *) 

이것은 당신이 무엇을하려고처럼 많이 보이는, 컴파일러에서 환경을 처리하는 일반적인 방법입니다. OCaml은 함축적 인 매개 변수를 지원하지 않으므로 암시 적으로 상태를 스레드하지 않습니다. 그러나 단 하나의 "환경"매개 변수 만 필요로하면 모든 적절한 기능에 추가하기가 너무 번거롭지 않습니다.

3

모나드도 OCaml에서 작동합니다. pa_monad_custom 구문 확장 덕분에 do-notation을 사용할 수도 있습니다. 대부분의 경우 바인드 연산자가 중위 인 경우 (>>=) 멋진 코드를 작성하기에 충분합니다.

let make_unique_id init = 
    let s = ref (init - 1) (* :-) *) in 
    fun() -> 
    incr s; 
    !s 

s입니다 : 당신이 특정 방법을 통해서만 변경 해당 지역의 상태를 제한하려면

+0

그러나 실제로 사람들은 이런 종류의 코드에 모나드를 사용합니까? 이 작업을 수행하는 프로젝트를 생각해 보면 내 인생을 훨씬 쉽게 만들어 줄 것입니다. – hugomg

+0

보통 우리는 입출력을 위해 모나드를 사용합니다. 개인적으로 프로덕션 코드에서 상태 모나드를 보지 못했습니다. 내 개인적인 취향은 명시 적으로 상태를 전달하고 변경 될 때 반환하는 것입니다. – ivg

1

당신의 코드는이 지역의 맥락에서 숨길해야 ... 모나드 스타일 + 참조의 이상한 혼합물처럼 보인다 이제는 클로저에 숨겨져 있습니다. "(모든 호출을 당신은 전역 변수는 바람직하지 않은 제안 당신이 전역 변수를

let currentId = ref 0 

let uniqueId() = 
    incr currentId; 
    !currentId 

을 원하는처럼

let() = 
    let x1 = 
    let unique_id = make_unique_id 1 in 
    let i = unique_id() in 
    i 
    in 
    print_int x1; print_newline(); (* Prints 1 *) 

    let x2 = 
    let unique_id = make_unique_id 1 in 
    let i = unique_id() in  (* 1 *) 
    let j = unique_id() in  (* 2 *) 
    i + j 
    in 
    print_int x2; print_newline() (* Prints 3 *) 
+0

makeBody의 콜백이 다른 함수를 호출하면, 그것들에'unique_id'를 넘겨 줄 필요가 있습니다. 그리고 우리는 시작했습니다 ... 내가 얻는 인상은 이것이 상태를 OO 패턴으로 캡슐화한다는 것입니다 그리고 죄수)하지만 원래의 "주위에 상태를 스레딩"문제를 해결하지 못했습니다. 괴괴 망측 한 참고 + 모나드 스타일에 관해서는, 내가 질문하는 이유는 무엇입니까? 나는 mutable 상태를 사용하는 것이 모나드를 통해 가짜 상태를 사용하는 것보다 더 깔끔하다고 생각하지만, 나는 ocaml을 처음 접했고 그래서 나는 무엇을 알고 있는가. – hugomg

+0

하나의'unique_id' 함수가 두 개 이상의 장소에서 오용되는 것을 막으려면, 인수가 신선한 것을 취하는'with_unique_id : ((unit -> int) -> 'a) ->'a'와 같은 고차 함수를 정의하십시오 함수는'make_unique_id'에 의해 만들어진다. BTW, 순수한 상태 모나드 솔루션을 원한다면, 순수한 상태로 유지해야합니다. 모나드 상태에 대한 참조는 매우 혼란 스럽습니다. – camlspotter

+0

레오 화이트의 답변에서 제시 한 것처럼 주를 추상적 인 유형으로 지정하더라도 레퍼런스를 사용하고 있습니까? – hugomg

0

그것은 소리지만 행동은 당신이 지정 따라서 당신은 독립적 인 서로 카운터를 만들 수 있습니다 uniqueId가 동일한 상태 매개 변수를 전달 ")는 전역 변수의 동작입니다.

글로벌 변수에 액세스하는 다른 코드가 걱정되면 currentId을 모듈의 서명 (.mli 파일)에 노출시키지 마십시오.

let uniqueId = 
    let currentId = ref 0 in 
    fun() -> 
     incr currentId; 
     !currentId 

또는 currentId을 노출하지 않는 서브 모듈을 만들 : 당신은 같은 모듈은 다음 uniqueId의 정의에 배치하여 그 범위를 제한 할 수 있습니다 currentId 접근 내의 다른 코드에 대해 우려하는 경우

서명에 :

module M : sig 

    val uniqueId : unit -> int 

end = struct 

    let currentId = ref 0 

    let uniqueId() = 
    incr currentId; 
    !currentId 

end 

include M 

개인적으로는 최초의 솔루션합니다 (.mli 파일에 숨겨진 전역 변수)로 갈 것입니다. 동일한 모듈 내의 다른 코드가 currentId을 남용하지 않고 모듈 시스템이 다른 곳에서 코드를 보호 할 수 있는지 확인하는 것은 어렵지 않습니다.

+0

나는 이것이 내가 원하는 것을 정확하게하고 있는지 확신하지 못한다. 'makeThings'에 대한 각각의 호출은 ID가 전역 적으로 증가하는 ID 대신에 0으로부터 ID를 생성하기 시작해야합니다. (실제 사용 사례에서는 상태가 makeThings 호출 사이에 지워질 필요가있는 목록이기 때문입니다).또한 모듈을 사용하여 상태를 캡슐화하는 경우 동일한 모듈의 두 인스턴스를 인스턴스화하는 방법이 있습니까? 아니면 단일 statefull 인스턴스로 제한됩니까? – hugomg

+0

functor 또는 first-class 모듈을 사용하여 모듈을 사용하여 여러 인스턴스로 상태를 캡슐화 할 수 있습니다. 그러나이 경우에는 아무 것도 얻을 수 없으며'updateId'를 그냥 지나칠 수도 있습니다. –

+0

상태 매개 변수를 명시 적으로 전달해야하지만 여러 인스턴스를 지원하는 다른 답변을 추가했습니다. OCaml은 함축적 인 매개 변수를 지원하지 않으므로 이것을 피할 수는 없습니다. –

1

나는 당신이 달성하고자하는 것 같다 :

  1. state이있는 기능 f (make_things). 당신이 f 전화
  2. 매번, 상태는 대신, 상태가 자동으로 내가 올바른 생각하면

은, 우리가 Monald 필요하지 않습니다 변경할 수 있습니다 f 내부에 하나 개의 통화 중

  • 하지만를 다시 얻는다 우리는 메모을 사용할 수 있습니다.

    let memo_incr_state() = 
        let s = ref 0 in 
        fun() -> s := !s + 1; !s 
    
    let make_things f = 
        let ms = memo_incr_state() in 
        f ms 
    
    let f1 ms = 
        let i = ms() in 
        let j = ms() in 
        i+j 
    
    let x1 = make_things f1 (* x1 should be 3 *) 
    

    기본 아이디어는 썽크를 사용하여 상태를 기억하는 것입니다.

    암기에 대한

    더 많은 지식은 귀하의 질문은 조금 명확하지, espcially 당신이 사 글로벌 variable`을 당신의 마음에 '언급하지만, 레오 화이트에 귀하의 코멘트에 당신은 당신이하지 않는 말했다입니다 https://realworldocaml.org/v1/en/html/imperative-programming-1.html#memoization-and-dynamic-programming