답변:
그것이 저에게 제시된 방식, 그리고 지금 한 달 동안 Haskell에서 학습 한 후 사실이라고 생각하는 것은 함수형 프로그래밍이 흥미로운 방식으로 당신의 두뇌를 뒤틀린다는 사실입니다. 익숙한 문제에 대해 여러 가지 방식으로 생각하게합니다. : 루프 대신 맵과 접기 및 필터 등으로 생각하십시오. 일반적으로 문제에 대해 둘 이상의 관점이있는 경우이 문제에 대해 추론하고 필요에 따라 관점을 전환 할 수 있습니다.
Haskell의 또 다른 멋진 점은 타입 시스템입니다. 엄격하게 유형이 지정되어 있지만 유형 추론 엔진은 바보 같은 유형 관련 실수를했을 때 마술처럼 알려주는 Python 프로그램처럼 느껴집니다. 이와 관련하여 Haskell의 오류 메시지는 다소 부족하지만 언어에 익숙해지면 스스로에게 말할 것입니다 : 이것이 타이핑이되어야하는 것입니다!
이것은 내가 Haskell을 배우도록 설득 한 예입니다.
-- program to copy a file --
import System.Environment
main = do
--read command-line arguments
[file1, file2] <- getArgs
--copy file contents
str <- readFile file1
writeFile file2 str
좋습니다. 짧고 읽기 쉬운 프로그램입니다. 그런 의미에서 C 프로그램보다 낫습니다. 그러나 이것이 매우 유사한 구조를 가진 파이썬 프로그램과 어떻게 다른가요?
대답은 게으른 평가입니다. 대부분의 언어 (일부 기능적 언어 포함)에서 위와 같이 구조화 된 프로그램은 전체 파일이 메모리에로드 된 다음 새 이름으로 다시 작성됩니다.
Haskell은 "게으른"사람입니다. 필요할 때까지 계산하지 않으며, 더 이상 필요 하지 않은 것을 계산 하지 않습니다 . 예를 들어, 만약 당신이 writeFile
줄 을 제거한다면 , Haskell은 처음에 파일에서 아무것도 읽지 않을 것입니다.
그대로 Haskell은가에 writeFile
의존 한다는 것을 인식 readFile
하고이 데이터 경로를 최적화 할 수 있습니다.
결과는 컴파일러에 따라 다르지만 위 프로그램을 실행할 때 일반적으로 발생하는 일은 다음과 같습니다. 프로그램은 첫 번째 파일의 블록 (예 : 8KB)을 읽은 다음 두 번째 파일에 쓴 다음 첫 번째 파일에서 다른 블록을 읽습니다. 두 번째 파일에 씁니다. (실행 해보세요 strace
!)
... 이것은 파일 복사의 효율적인 C 구현이하는 것과 매우 유사합니다.
따라서 Haskell을 사용하면 많은 성능을 저하시키지 않고 간결하고 읽기 쉬운 프로그램을 작성할 수 있습니다.
내가 추가해야 할 또 다른 것은 Haskell이 단순히 버그가있는 프로그램을 작성하는 것을 어렵게 만든다는 것입니다. 놀라운 유형 시스템, 부작용 부족, 물론 Haskell 코드의 간결함은 최소한 세 가지 이유로 버그를 줄입니다.
더 나은 프로그램 디자인. 복잡성이 감소하면 논리 오류가 줄어 듭니다.
간결한 코드. 버그가있을 줄이 적습니다.
컴파일 오류. 많은 버그 는 유효한 Haskell이 아닙니다 .
Haskell이 모든 사람을위한 것은 아닙니다. 그러나 모두가 그것을 시도해야합니다.
hSetBuffering handle (BlockBuffering (Just bufferSize))
.
Data.Bytestring.Lazy.readFile
하스켈이 게으른 (엄격하지 않은) 언어 인 하스켈과 관련이없는 게으른 바이트 스트링 (로 할 수있는 ) 을 사용하지 않는 한 전체 파일 을 읽습니다 . 모나드는 시퀀싱입니다 . 이것은 대략 "결과를 꺼내면 모든 부작용이 수행됩니다"를 의미합니다. "lazy Bytestring"마법에 관해서는 위험합니다. 대부분의 다른 언어에서 비슷하거나 더 간단한 구문으로 할 수 있습니다.
readFile
도 같은 방식으로 게으른 IO를 Data.ByteString.Lazy.readFile
수행합니다. 따라서 대답은 틀린 것이 아니며 단순히 컴파일러 최적화가 아닙니다. 실제로 이것은 Haskell 사양의 일부입니다 . "이 readFile
함수는 파일을 읽고 파일의 내용을 문자열로 반환합니다. 파일은에서와 같이 요청시 느리게 읽 힙니다 getContents
."
const fs = require('fs'); const [file1, file2] = process.argv.slice(2); fs.createReadStream(file1).pipe(fs.createWriteStream(file2))
. 배쉬도 비슷한 일이 있습니다cat $1 > $2
당신은 잘못된 질문을하고 있습니다.
하스켈은 몇 가지 멋진 예제에서 살펴 가서가는 언어가 아닙니다 "아하, 내가 지금 참조 입니다 그것은 좋은 만드는 것!"
마치, 우리는 다른 모든 프로그래밍 언어를 가지고 있고, 그것들은 모두 다소 비슷합니다. 그리고 완전히 다르고 엉뚱한 Haskell이 있습니다. 일단 당신이 괴팍함에 익숙해지면 완전히 굉장합니다. 그러나 문제는 그 괴팍함에 적응하는 데 꽤 오랜 시간이 걸린다는 것입니다. Haskell을 다른 거의 모든 준 주류 언어와 차별화시키는 요소 :
뿐만 아니라 많은 주류 언어와는 다른 (하지만 일부에서 공유하는) 몇 가지 다른 측면 :
다른 포스터가 대답했듯이 이러한 모든 기능의 조합은 완전히 다른 방식으로 프로그래밍에 대해 생각한다는 것을 의미합니다. 그래서 이것을 Joe-mainstream-programmer에게 적절하게 전달하는 예 (또는 일련의 예)를 생각해 내기는 어렵습니다. 그것은 경험적인 것입니다. (비유하자면 1970 년 중국 여행 사진을 보여 드릴 수는 있지만 사진을보고 나면 그 기간 동안 그곳에서 살았던 것이 어떤 것인지 알 수 없습니다. 마찬가지로 Haskell을 보여 드릴 수 있습니다. 'quicksort'이지만 Haskeller라는 것이 무엇을 의미하는지 여전히 알 수 없습니다.)
Haskell을 진정으로 차별화하는 것은 함수형 프로그래밍을 적용하기 위해 설계에서들이는 노력입니다. 거의 모든 언어로 기능적인 스타일로 프로그래밍 할 수 있지만, 처음에는 포기하기가 너무 쉽습니다. Haskell은 함수형 프로그래밍을 포기하는 것을 허용하지 않으므로 논리적 인 결론에 도달해야합니다. 이는 추론하기 더 쉬운 최종 프로그램이며 가장 까다로운 유형의 버그 전체를 피해야합니다.
실제 사용을위한 프로그램을 작성할 때 Haskell이 실용적인 방식이 부족하다는 것을 알 수 있지만, 처음부터 Haskell을 아는 것이 최종 솔루션이 더 좋습니다. 나는 아직 거기에 있지 않지만 지금까지 Haskell을 배우는 것은 Lisp가 대학에 있었다고 말하는 것보다 훨씬 더 깨달았습니다.
unsafePerformIO
바로 세계 화상을보고 싶은 사람들을 위해이)
소란의 일부는 순도와 정적 타이핑이 공격적인 최적화와 결합 된 병렬성을 가능하게한다는 것입니다. 병렬 언어는 이제 멀티 코어가 약간 파괴적이어서 뜨겁습니다.
Haskell은 빠른 네이티브 코드 컴파일러와 함께 거의 모든 범용 언어보다 더 많은 병렬 처리 옵션을 제공합니다. 병렬 스타일에 대한 이러한 종류의 지원에는 경쟁이 없습니다.
따라서 멀티 코어 작업에 관심이 있다면 Haskell이 할 말이 있습니다. 시작하기 좋은 곳 은 Haskell의 병렬 및 동시 프로그래밍에 대한 Simon Peyton Jones의 자습서입니다 .
소프트웨어 트랜잭션 메모리 는 동시성을 처리하는 매우 멋진 방법입니다. 메시지 전달보다 훨씬 유연하며 뮤텍스처럼 교착 상태가 발생하지 않습니다. GHC의 STM 구현은 최고 중 하나로 간주됩니다.
저는 작년에 Haskell을 배우고 그 안에 상당히 크고 복잡한 프로젝트를 작성하는 데 보냈습니다. (이 프로젝트는 자동화 된 옵션 거래 시스템이며 거래 알고리즘에서 저수준, 고속 시장 데이터 피드의 구문 분석 및 처리에 이르기까지 모든 것이 Haskell에서 수행됩니다.) 훨씬 더 간결하고 이해하기 쉽습니다. 적절한 배경)은 Java 버전보다 매우 강력합니다.
아마도 저에게 가장 큰 승리는 모노 이드, 모나드 등과 같은 것을 통해 제어 흐름을 모듈화하는 능력이었습니다. 아주 간단한 예는 Ordering monoid입니다. 다음과 같은 표현에서
c1 `mappend` c2 `mappend` c3
어디 c1
그래서 반환에 LT
, EQ
또는 GT
, c1
반환 EQ
원인을 식을 평가, 계속 c2
; c2
반환 되는 경우 LT
또는 GT
그것은 전체의 값이며 c3
평가되지 않습니다. 이런 종류의 것은 모나 딕 메시지 생성기 및 구문 분석기와 같은 것에서 상당히 더 정교하고 복잡해집니다. 여기서는 여러 유형의 상태를 처리하거나, 다양한 중단 조건을 가지고 있거나, 중단이 실제로 의미하는지 여부를 특정 호출에 대해 결정할 수 있기를 원할 수 있습니다. "추가 처리 안 함"또는 "마지막에 오류를 반환하지만 추가 오류 메시지 수집을 위해 계속 처리"를 의미합니다.
이것은 모두 배우는 데 약간의 시간과 노력이 필요하므로 이러한 기술을 아직 모르는 사람들을 위해 설득력있는 주장을하기가 어려울 수 있습니다. 모나드 의 모든 것 튜토리얼이 이것의 한 측면에 대한 꽤 인상적인 시연을 제공 한다고 생각 하지만, 이미 자료에 익숙하지 않은 사람이 이미 첫 번째 또는 심지어 세 번째,주의 깊게 읽었을 때 "그것을 얻을"것이라고는 기대하지 않습니다.
어쨌든 Haskell에는 다른 좋은 것들이 많이 있지만, 이것은 내가 자주 언급하지 않는 주요한 것입니다. 아마도 그것은 다소 복잡하기 때문일 것입니다.
흥미로운 예는 http://en.literateprograms.org/Quicksort_(Haskell) 에서 볼 수 있습니다.
흥미로운 것은 다양한 언어로 구현 된 것을 보는 것입니다.
Haskell을 다른 기능적 언어와 함께 흥미롭게 만드는 것은 프로그래밍 방법에 대해 다르게 생각해야한다는 사실입니다. 예를 들어 일반적으로 for 또는 while 루프를 사용하지 않지만 재귀를 사용합니다.
위에서 언급했듯이 Haskell 및 기타 기능 언어는 병렬 처리 및 다중 코어에서 작동하는 응용 프로그램 작성에 탁월합니다.
예를 들어 드릴 수 없었습니다. 저는 OCaml 녀석입니다.하지만 제가 여러분과 같은 상황에 처하면 호기심이 붙을뿐입니다. 컴파일러 / 통역사를 다운로드하여 시도해보아야합니다. 주어진 기능적 언어의 강점과 약점에 대해 훨씬 더 많이 배울 것입니다.
알고리즘이나 수학적 문제를 다룰 때 내가 매우 멋지다고 생각하는 것은 Haskell의 고유 한 계산에 대한 게으른 평가인데, 이는 엄격한 기능적 특성으로 인해 가능합니다.
예를 들어 모든 소수를 계산하려면 다음을 사용할 수 있습니다.
primes = sieve [2..]
where sieve (p:xs) = p : sieve [x | x<-xs, x `mod` p /= 0]
결과는 실제로 무한 목록입니다. 그러나 Haskell은 오른쪽에서 왼쪽으로 평가하므로 전체 목록이 필요한 작업을 수행하지 않는 한 다음과 같이 프로그램이 무한대에 갇히지 않고 계속 사용할 수 있습니다.
foo = sum $ takeWhile (<100) primes
이것은 100보다 작은 모든 소수를 합산합니다. 이것은 여러 가지 이유로 좋습니다. 우선, 모든 소수를 생성하는 하나의 소수 함수 만 작성하면됩니다. 그러면 소수로 작업 할 준비가 거의되었습니다. 객체 지향 프로그래밍 언어에서는 반환하기 전에 몇 개의 소수를 계산해야하는지 함수에 알려주거나 객체로 무한 목록 동작을 에뮬레이트하는 방법이 필요합니다. 또 다른 한 가지는 일반적으로 계산하려는 것을 표현하는 코드를 작성하는 것이며, 평가할 순서가 아니라 컴파일러가 대신 수행한다는 것입니다.
이것은 무한 목록에 유용 할뿐만 아니라 실제로 필요 이상으로 평가할 필요가 없을 때 항상 알지 못하는 사이에 사용됩니다.
나는 몇 가지 작은 예를 보는 것이 Haskell을 과시하는 가장 좋은 방법이 아니라는 데 동의합니다. 하지만 어쨌든 조금 줄 게요. 다음은 오일러 프로젝트 문제 18 및 67에 대한 초고속 솔루션 으로, 삼각형의 밑면에서 정점까지의 최대 합 경로를 찾도록 요청합니다.
bottomUp :: (Ord a, Num a) => [[a]] -> a
bottomUp = head . bu
where bu [bottom] = bottom
bu (row : base) = merge row $ bu base
merge [] [_] = []
merge (x:xs) (y1:y2:ys) = x + max y1 y2 : merge xs (y2:ys)
다음은 Lesh와 Mitzenmacher가 작성한 BubbleSearch 알고리즘 의 완전하고 재사용 가능한 구현입니다 . DVD에 보관 용으로 대용량 미디어 파일을 낭비없이 포장하는 데 사용했습니다.
data BubbleResult i o = BubbleResult { bestResult :: o
, result :: o
, leftoverRandoms :: [Double]
}
bubbleSearch :: (Ord result) =>
([a] -> result) -> -- greedy search algorithm
Double -> -- probability
[a] -> -- list of items to be searched
[Double] -> -- list of random numbers
[BubbleResult a result] -- monotone list of results
bubbleSearch search p startOrder rs = bubble startOrder rs
where bubble order rs = BubbleResult answer answer rs : walk tries
where answer = search order
tries = perturbations p order rs
walk ((order, rs) : rest) =
if result > answer then bubble order rs
else BubbleResult answer result rs : walk rest
where result = search order
perturbations :: Double -> [a] -> [Double] -> [([a], [Double])]
perturbations p xs rs = xr' : perturbations p xs (snd xr')
where xr' = perturb xs rs
perturb :: [a] -> [Double] -> ([a], [Double])
perturb xs rs = shift_all p [] xs rs
shift_all p new' [] rs = (reverse new', rs)
shift_all p new' old rs = shift_one new' old rs (shift_all p)
where shift_one :: [a] -> [a] -> [Double] -> ([a]->[a]->[Double]->b) -> b
shift_one new' xs rs k = shift new' [] xs rs
where shift new' prev' [x] rs = k (x:new') (reverse prev') rs
shift new' prev' (x:xs) (r:rs)
| r <= p = k (x:new') (prev' `revApp` xs) rs
| otherwise = shift new' (x:prev') xs rs
revApp xs ys = foldl (flip (:)) ys xs
이 코드는 임의의 횡설수설처럼 보입니다. 그러나 Mitzenmacher의 블로그 항목 을 읽고 알고리즘을 이해 한다면 검색하려는 내용에 대해 아무 말없이 알고리즘을 코드로 패키지화 할 수 있다는 사실에 놀랄 것입니다.
요청한대로 몇 가지 예를 들었 으므로 Haskell을 이해 하는 가장 좋은 방법 은 DVD 패커를 작성하는 데 필요한 아이디어를 제공하는 논문을 읽는 것 입니다. John Hughes의 기능적 프로그래밍이 중요한 이유 . 이 논문은 실제로 Haskell보다 앞서 있지만 Haskell과 같은 사람들을 만드는 아이디어 중 일부를 훌륭하게 설명합니다.
저에게 Haskell의 매력은 컴파일러가 정확성을 보장 한다는 약속입니다 . 코드의 순수한 부분을위한 것이더라도.
나는 많은 과학적 시뮬레이션 코드를 작성했고, 이전 코드에 버그가 있었는지, 많은 현재 작업을 무효화 할 수 있는지에 대해 여러 번 궁금해 했습니다.
특정 작업에서 Haskell을 사용하면 생산성이 매우 높습니다.
그 이유는 간결한 구문과 테스트의 용이성 때문입니다.
함수 선언 구문은 다음과 같습니다.
foo a = a + 5
이것이 제가 함수를 정의 할 수있는 가장 간단한 방법입니다.
역으로 쓰면
inverseFoo a = a-5
나는 작성하여 임의의 입력에 대해 역인지 확인할 수 있습니다.
prop_IsInverse :: Double-> Bool
prop_IsInverse a = a == (inverseFoo $ foo a)
그리고 명령 줄에서 호출
jonny @ ubuntu : runhaskell quickCheck + names fooFileName.hs
입력을 무작위로 테스트하여 내 파일의 모든 속성이 유지되는지 확인합니다.
Haskell이 모든 것을위한 완벽한 언어라고 생각하지는 않지만, 작은 함수를 작성하고 테스트 할 때 더 좋은 것을 보지 못했습니다. 프로그래밍에 수학적 구성 요소가 있다면 이것은 매우 중요합니다.
Haskell의 유형 시스템에 머리를 감쌀 수 있다면 그 자체로 상당한 성취라고 생각합니다.
반대되는 견해를 표명 하기 위해 : Steve Yegge는 Hindely-Milner 언어가 좋은 시스템을 작성하는 데 필요한 유연성이 부족하다고 썼습니다 .
HM은 완전히 쓸모없는 형식적인 수학적 의미에서 매우 예쁩니다. 몇 가지 계산 구조를 매우 잘 처리합니다. Haskell, SML 및 OCaml에서 발견되는 패턴 매칭 디스패치가 특히 편리합니다. 당연히 다른 일반적이고 매우 바람직한 구조를 기껏해야 어색하게 처리하지만, 그들은 당신이 착각하고 실제로 원하지 않는다고 말함으로써 이러한 시나리오를 설명합니다. 아시다시피, 오, 변수 설정과 같은 것들이 있습니다.
Haskell은 배울 가치가 있지만 나름의 약점이 있습니다.