2013-02-19 2 views
2

저는 f #을 처음 접했고 주어진 dir의 모든 파일과 ".txt"유형의 각 파일에 대해 ID 번호 + "DONE"을 파일에 추가해야하는 프로그램을 작성하려고했습니다."printf"없이 Seq.whatever에서 함수를 호출하는 방법은 무엇입니까?

내 프로그램 :

//const: 
[<Literal>] 
let notImportantString= "blahBlah" 
let mutable COUNT = 1.0 

//funcs: 
//addNumber --> add the sequence number COUNT to each file. 
let addNumber (file : string) = 
let mutable str = File.ReadAllText(file) 
printfn "%s" str//just for check 
let num = COUNT.ToString() 
let str4 = str + " " + num + "\n\n\n DONE" 
COUNT <- COUNT + 1.0 
let str2 = File.WriteAllText(file,str4) 
file 

//matchFunc --> check if is ".txt" 
let matchFunc (file : string) = 
file.Contains(".txt") 

//allFiles --> go through all files of a given dir 
let allFiles dir = 

seq 
    { for file in Directory.GetFiles(dir) do 
     yield file 
      } 

//////////////////////////// 

let dir = "D:\FSharpTesting" 
let a = allFiles dir 
     |> Seq.filter(matchFunc) 
     |> Seq.map(addNumber) 
printfn "%A" a 

내 질문 :.

은 Tf를 내가 (printfn "% A는"A) 파일이 변경되지 않습니다 (내가이 줄을 쓰는 경우 마지막 줄을 쓰지 않는다 그것은 작동하고 파일을 변경) 나는 디버거를 사용할 때 "a ="...... "가되면 printfn을 계속하면 라인에 도착했을 때 'a'값을 실제로 계산하지 않는다는 것을 알 수있다. "a"가 보일 때보 다 "a"의 답을 계산합니다. 왜 그런데 어떻게 인쇄없이 기능을 "시작"할 수 있습니까 ??

도 - "addNumber"함수의 반환 유형으로 파일을 추가해야하는 이유는 무엇입니까? 내가 [] 정의 의 선 후 COUNT 변수 권리를 쓰는 경우가 있습니다

마지막 질문 - 을 (그 때문에 어떻게 작동하지만 난 정말 .... 이유를 이해하지 않는이 추가) 오류가 발생하고 상수는 "변경 가능"할 수 없다고 말합니다. 그러나 문자열을 추가하기 전에 다른 줄을 추가하면 실수와 작동을 "잊어 버립니다". 그 이유는 무엇입니까? 그리고 만약 당신이 정말 const 변수를 가질 수 없어 정적 변수를 어떻게 할 수 있습니까?

답변

9

마지막 줄을 쓰지 않으면 (printfn "% A"a) 파일이 변경되지 않습니다.

F # 시퀀스는 게으르다. 따라서 평가를 강제하려면 시퀀스를 반환하지 않는 일부 작업을 실행할 수 있습니다. 예를 들어, 등 또는 Seq.toList (목록 열망 데이터 구조를 반환) (상기 시퀀스의 길이 인 int을 반환) Seq.length을 (() 반환 부작용이 있음) Seq.iter 호출 할 수

어떤 사람이 "addNumber"함수의 반환 유형으로 file : string을 추가해야하는 이유를 말해 줄 수 있습니까?

메서드 및 속성 액세스가 F # 형식 유추에 적합하지 않습니다. 유형 검사기는 위에서 아래로 왼쪽에서 오른쪽으로 작동합니다. file.Contains라고하면 Contains 멤버로 어떤 유형인지 알려지지 않습니다. 따라서 형식 주석은 F # 형식 검사기에 대한 좋은 힌트입니다.

나는 그것이 오류를 제공하고 상수

MSDN에서 인용 "가변"이 될 수 없다고 말한다 [<Literal>] 정의 의 선 후 COUNT 변수를 잘 작성하는 경우 :

상수로 사용하려는 값은 Literal 특성으로 표시 할 수 있습니다. 이 속성은 값을 상수로 컴파일하게하는 효과가 있습니다.

변경할 수있는 값은 프로그램의 어느 시점에서 값을 변경할 수 있습니다. 컴파일러는 좋은 이유에 대해 불평합니다. [<Literal>] 속성을 삭제하면됩니다.

+0

좋아, 알았어! 감사합니다. – nati

1

f #은 위에서 아래로 평가되지만, printfn을 수행 할 때까지는 게으른 값만 작성합니다. 그래서 printfn은 실제로 실행 된 첫 번째 것이고 나머지 코드는 차례대로 실행됩니다. Seq.map (addNumber) 다음에 println을 붙이고 toList를 사용하면 동일한 결과를 얻을 수 있다고 생각합니다.

+0

고맙습니다. 나는 진실 된 대답을 진짜로 인정한다! – nati

3

Seq.map은 하나의 값을 다른 값으로 매핑하기위한 것이지 일반적으로 값을 변경하는 것이 아닙니다. seq<_>은 지연 생성 시퀀스를 나타내므로 Alex가 지적했듯이 시퀀스가 ​​열거 될 때까지 아무 것도 일어나지 않습니다. 이것은 아마 더 잘 맞는 codereview에 대한,하지만 여기에 내가 이것을 쓰는 것입니다 방법은 다음과 같습니다

Directory.EnumerateFiles(dir, "*.txt") 
    |> Seq.iteri (fun i path -> 
    let text = File.ReadAllText(path) 
    printfn "%s" text 
    let text = sprintf "%s %d\n\n\n DONE" text (i + 1) 
    File.WriteAllText(path, text)) 

Seq.map는 반환 유형을 필요로 F 번호의 모든 표현을 할 수있다. 함수가 값을 계산하는 것과 달리 작업을 수행하는 경우 unit : ()을 반환 할 수 있습니다. COUNT의 경우 값은 mutable[<Literal>] (C#의 경우 const) 일 수 없습니다. 그것들은 정반대입니다.

module Counter = 
    let mutable count = 1 

open Counter 
count <- count + 1 

하지만 당신은 count의 개인 구현의 일환으로 카운터 변수와 함수를 만들어 글로벌 가변 데이터를 피할 수 있습니다 : 정적 변수를 들어,이 모듈 범위의 결합 let mutable를 사용합니다. 종결 코드로이 작업을 수행 할 수 있습니다.

let count = 
    let i = ref 0 
    fun() -> 
    incr i 
    !i 

let one = count() 
let two = count() 
+0

귀하의 답변에 감사드립니다! – nati

3

F # 시퀀스는 느리게 평가됩니다. 즉, 시퀀스의 각 요소가 "필요시"생성됩니다.

이점은 필요하지 않은 요소에 계산 시간과 메모리를 낭비하지 않는다는 것입니다. 게으른 평가는 약간 익숙해지고 있습니다. 특히 실행 순서를 정할 수 없기 때문에 (또는 실행이 전혀 발생하지 않기 때문입니다.)

문제는 간단하게 해결할 수 있습니다 : Seq.iter을 사용하여 시퀀스의 실행/평가를 강제하고 '무시'기능을 전달하십시오. 왜냐하면 시퀀스에서 반환 한 값을 신경 쓰지 않기 때문입니다.

let a = allFiles dir 
    |> Seq.filter(matchFunc) 
    |> Seq.map(addNumber) 
    |> Seq.iter ignore // Forces the sequence to execute 
+1

'iter'가 함수를 기대하기 때문에 아마'Seq.iter ignore'가 필요합니다. – Daniel

+0

감사합니다 !!!!!! – nati

+0

@Daniel 당신은 절대적으로 맞습니다. 이제 해결되었습니다. –

1

이것은 일반적으로 지연 시퀀스의 동작입니다. C#에서 IEnumerable을 사용하여 seq가 별칭 인 경우와 동일합니다. 의사 코드에서 는 :

이 시퀀스는 단지 설명이며,이 열거 될 때를 말하지 않는다는 사실을 설명 :

var lazyseq = "abcdef".Select(a => print a); //does not do anything 
    var b = lazyseq.ToArray(); //will evaluate the sequence 

ToArray는 일련의 평가를 트리거이 시퀀스의 소비자을 제어합니다.


주제에 조금 더 이동하려면, 당신은 this page from F# wikibook보고 할 수 있습니다 :

let isNebraskaCity_bad city = 
    let cities = 
     printfn "Creating cities Set" 
     ["Bellevue"; "Omaha"; "Lincoln"; "Papillion"] 
     |> Set.ofList 

    cities.Contains(city) 

let isNebraskaCity_good = 
    let cities = 
     printfn "Creating cities Set" 
     ["Bellevue"; "Omaha"; "Lincoln"; "Papillion"] 
     |> Set.ofList 

    fun city -> cities.Contains(city) 

가장 두드러진 순서가 가 캐시되지 않습니다 (당신이 그렇게 그들을 만들 수 있지만). 다음으로, 시퀀스 자체를 다시 계산할 때 설명과 런타임 비헤이비어 간의 차이점은 중요한 결과를 가져올 수 있습니다. 매우 높은 비용이 발생할 수 있으며 각 값이 선형이면 얻을 수있는 2 차 연산 수를 도입 할 수 있습니다!

+0

감사합니다 everyOne !!! 그것의 나의 첫번째 시간 여기에서 질문을 게시하고 나는 대답을 너무 빨리 얻을 것을 기대하지 않았다! :) 마지막 질문에 대한 답변을 누구 한테 알고 있습니까? – nati

+0

리터럴은 실제로 바이트 코드로 인코딩 된 값의 바이너리 표현을 가질 것입니다. 그것은 변수가 아니라 PI와 같은 실제 상수입니다. 이름 PI는 하드 코딩 된 값의 바로 가기입니다. – nicolas

+0

@ user2088397 : 정적 변수를 보여주기 위해 내 대답에 몇 가지 코드를 추가했습니다. – Daniel