Haskell Language하스켈 언어 시작하기

비고

하스켈 로고

Haskell 은 고급 순수 함수형 프로그래밍 언어입니다.

풍모:

  • 정적 형식 : 하스켈의 모든 식은 컴파일 할 때 결정되는 형식을가집니다. 정적 유형 검사는 프로그램의 텍스트 (소스 코드)를 분석하여 프로그램의 유형 안전성을 검증하는 프로세스입니다. 프로그램이 정적 유형 검사기를 통과하면 프로그램은 가능한 모든 입력에 대해 유형 안전성 속성 집합을 만족하도록 보장됩니다.
  • 순수 기능 : 하스켈의 모든 기능은 수학적 의미에서의 기능이다. 진술이나 지시 사항이 없으며, 변수 (로컬 또는 글로벌)를 변경하거나 시간이나 난수와 같은 상태에 액세스 할 수없는 표현식 만 있습니다.
  • 동시성 : 주력 컴파일러 인 GHC에는 고성능 병렬 가비지 수집기와 여러 가지 유용한 동시성 기본 및 추상화가 포함 된 경량 동시성 라이브러리가 함께 제공됩니다.
  • 게으른 평가 : 함수는 인수를 평가하지 않습니다. 값이 필요할 때까지 표현식 평가를 지연합니다.
  • 범용 : 하스켈은 모든 환경과 환경에서 사용될 수 있도록 제작되었습니다.
  • 패키지 : Haskell에 대한 오픈 소스 기여는 공공 패키지 서버에서 사용할 수있는 다양한 패키지로 매우 활발합니다.

Haskell의 최신 표준은 Haskell 2010입니다. 2016 년 5 월 현재 그룹은 Haskell 2020의 다음 버전을 개발 중입니다.

하스켈공식 문서 는 포괄적이고 유용한 자료이기도합니다. 책, 코스, 튜토리얼, 매뉴얼, 가이드 등을 찾을 수있는 좋은 장소

버전

번역 출시일
하스켈 2010 2012-07-10
하스켈 98 2002-12-01

안녕, 세상!

기본 "Hello, World!" Haskell의 프로그램 은 단지 하나 또는 두 줄로 간결하게 표현할 수 있습니다 :

main :: IO ()
main = putStrLn "Hello, World!"

첫 번째 줄 것을 나타내는 임의의 타입의 주석 인 main 유형의 값 IO () 타입의 값 "계산"은 I / O 동작을 나타내는, () ( "수단"을 판독하며 빈 튜플 더 정보를 전달하지 않음) 외부 세계에 대한 부작용을 수행하는 것 이외에는 (여기서는 터미널에서 문자열을 출력하는 것). 이 유형 주석은 대개 유일한 유형이기 때문에 main 대해 생략 됩니다 .

이것을 helloworld.hs 파일에 넣고 GHC와 같은 하스켈 컴파일러를 사용하여 컴파일합니다 :

ghc helloworld.hs

컴파일 된 파일을 실행하면 결과는 "Hello, World!" 화면에 인쇄되고 있습니다 :

./helloworld
Hello, World!

또는 runhaskell 또는 runghc 사용하면 컴파일하지 않고 해석 모드로 프로그램을 실행할 수 있습니다.

runhaskell helloworld.hs

대화식 REPL은 컴파일하는 대신 사용할 수도 있습니다. GHC 컴파일러와 함께 제공되는 ghci 와 같은 대부분의 Haskell 환경과 함께 제공됩니다.

ghci> putStrLn "Hello World!"
Hello, World!
ghci> 

또는 load (또는 :l )를 사용하여 파일에서 스크립트를 ghci로 load .

ghci> :load helloworld

:reload (또는 :r )는 ghci의 모든 것을 다시로드합니다.

Prelude> :l helloworld.hs 
[1 of 1] Compiling Main             ( helloworld.hs, interpreted )

<some time later after some edits>

*Main> :r
Ok, modules loaded: Main.

설명:

이 첫 번째 줄은 main 의 유형을 선언하는 형식 시그니처입니다.

main :: IO ()

IO () 유형의 값은 바깥 세상과 상호 작용할 수있는 활동을 설명합니다.

하스켈은 자동 타입 유추를 허용하는 Hindley-Milner 타입의 시스템 을 가지고 있기 때문에, 타입 시그너처는 기술적으로 선택 사항입니다 : main :: IO () 를 생략하면 컴파일러는 다음과 같이 타입을 추론 할 수 있습니다. main정의 를 분석합니다. 그러나 최상위 레벨 정의를위한 유형 시그니처를 작성하지 않는 것이 나쁜 스타일로 간주됩니다. 이유는 다음과 같습니다.

  • Haskell의 타입 서명은 타입 시스템이 매우 표현 적이기 때문에 문서의 매우 유용한 부분이다. 타입 시스템을 보면서 함수가 어떤 종류의 일을 하는지를 자주 볼 수 있기 때문이다. 이 "문서"는 GHCi와 같은 도구를 사용하여 편리하게 액세스 할 수 있습니다. 일반 문서와 달리 컴파일러의 유형 검사기는 함수 정의와 실제로 일치하는지 확인합니다.

  • 서명 유형은 로컬 버그를 유지합니다 . 형식 서명을 제공하지 않고 정의를 실수로 작성하면 컴파일러에서 오류를 즉시보고하지 않고 단순히 무의식 형식을 추론하여 실제로 형식을 검사합니다. 그런 다음 해당 값을 사용할 때 암호화되지 않은 오류 메시지가 나타날 수 있습니다. 서명을 사용하면 컴파일러는 버그가 발생한 곳에서 바로 발견 할 수 있습니다.

이 두 번째 줄은 실제 작업을 수행합니다.

main = putStrLn "Hello, World!"

당신이 명령형 언어에서 왔을 때,이 정의가 또한 다음과 같이 쓰여질 수 있다는 것을 알아두면 도움이 될 것입니다 :

main = do {
   putStrLn "Hello, World!" ;
   return ()
   }

또는 이와 비슷하게 (Haskell은 레이아웃 기반 파싱을 가지고 있지만,이 메커니즘을 혼란스럽게 할 수있는 탭과 공간을 일관되게 섞어서 는 안됩니다) :

main = do
    putStrLn "Hello, World!"
    return ()

do 블록의 각 행은 모나드 (여기서는 I / O) 계산을 나타내므로 전체 do 블록은 주어진 모나드에 특정한 방식으로 결합하여 이러한 하위 단계로 구성된 전체 동작을 나타냅니다 (I / O 이것은 단지 하나씩 차례대로 실행한다는 것을 의미합니다).

do 구문은 그 자체로 IO 와 같은 모나드의 문법적 설탕이며 return 은 특정 모나드 정의에 포함될 부작용이나 추가 계산을 수행하지 않고 그 인수를 생성하는 아무런 행동이 아닙니다.

위는 main = putStrLn "Hello, World!" 를 정의하는 것과 같습니다 main = putStrLn "Hello, World!" 왜냐하면 putStrLn "Hello, World!" 값이 putStrLn "Hello, World!" 이미 IO () 유형을 가지고 있습니다. "진술" putStrLn "Hello, World!" , putStrLn "Hello, World!" 완전한 프로그램으로 볼 수 있으며이 프로그램을 가리 키기 위해 main 을 정의하기 만하면됩니다.

putStrLn 의 서명을 온라인으로 검색 할 수 있습니다.

putStrLn :: String -> IO ()
-- thus,
putStrLn (v :: String) :: IO ()

putStrLn 은 문자열을 인수로 사용하여 I / O-action (런타임이 실행할 수있는 프로그램을 나타내는 값)을 출력하는 함수입니다. 런타임은 항상 main 이라는 작업을 실행하므로 putStrLn "Hello, World!" 와 동일하게 정의하면됩니다 putStrLn "Hello, World!" .

계승

계승 함수는 하스켈 "Hello World!"입니다. (그리고 일반적으로 함수형 프로그래밍을 위해) 언어의 기본 원칙을 간결하게 보여줍니다.

변형 1

fac :: (Integral a) => a -> a
fac n = product [1..n]

라이브 데모

  • Integral 은 정수 타입의 클래스입니다. IntInteger 예로들 수 있습니다.
  • (Integral a) => 타입에 제약 조건을 배치 에 있다고 클래스 a
  • fac :: a -> afac 은 a를 취하여 a 를 반환하는 함수라고 말합니다 a
  • product 은 모든 숫자를 곱하여 목록의 모든 숫자를 누적하는 함수입니다.
  • [1..n]enumFromTo 1 n 않는 특수 표기법이며 숫자 1 ≤ x ≤ n 의 범위입니다.

변형 2

fac :: (Integral a) => a -> a
fac 0 = 1
fac n = n * fac (n - 1)

라이브 데모

이 변형은 패턴 일치를 사용하여 함수 정의를 별도의 경우로 나눕니다. 인수가 0 (중지 조건이라고도 함)이고 두 번째 정의가 다른 경우 (정의 순서가 중요 함) 첫 번째 정의가 호출됩니다. 또한 fac 은 자체를 가리키는 것처럼 재귀를 보여줍니다.


재 작성 규칙으로 인해 두 버전의 fac 은 최적화가 활성화 된 상태에서 GHC를 사용할 때 동일한 기계 코드로 컴파일됩니다. 따라서 효율성 측면에서 볼 때이 둘은 동일합니다.

피보나치, 지연 평가 사용하기

게으른 평가는 Haskell이 값이 필요한 목록 항목 만 평가한다는 것을 의미합니다.

기본적인 재귀 적 정의는 다음과 같습니다.

f (0)  <-  0
f (1)  <-  1
f (n)  <-  f (n-1) + f (n-2)

직접 평가 한 경우 매우 느립니다. 그러나 모든 결과를 기록하는 목록이 있다고 가정 해 보겠습니다.

fibs !! n  <-  f (n) 

그때

                  ┌──────┐   ┌──────┐   ┌──────┐
                  │ f(0) │   │ f(1) │   │ f(2) │
fibs  ->  0 : 1 : │  +   │ : │  +   │ : │  +   │ :  .....
                  │ f(1) │   │ f(2) │   │ f(3) │
                  └──────┘   └──────┘   └──────┘

                  ┌────────────────────────────────────────┐
                  │ f(0)   :   f(1)   :   f(2)   :  .....  │ 
                  └────────────────────────────────────────┘
      ->  0 : 1 :               +
                  ┌────────────────────────────────────────┐
                  │ f(1)   :   f(2)   :   f(3)   :  .....  │
                  └────────────────────────────────────────┘

이것은 다음과 같이 코드화됩니다 :

fibn n = fibs !! n
    where
    fibs = 0 : 1 : map f [2..]
    f n = fibs !! (n-1) + fibs !! (n-2)

아니면

GHCi> let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
GHCi> take 10 fibs
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

zipWith 는 지정된 이진 함수를 주어진 두 목록의 해당 요소에 적용하여 목록을 만듭니다. 따라서 zipWith (+) [x1, x2, ...] [y1, y2, ...][x1 + y1, x2 + y2, ...] .

작성하는 또 다른 방법 fibs 함께 scanl 기능 :

GHCi> let fibs = 0 : scanl (+) 1 fibs
GHCi> take 10 fibs
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

scanlfoldl 이 생성하는 부분 결과 목록을 작성하고 입력 목록을 따라 왼쪽에서 오른쪽으로 작업합니다. 즉, scanl f z0 [x1, x2, ...][z0, z1, z2, ...] where z1 = f z0 x1; z2 = f z1 x2; ... 동일하다 [z0, z1, z2, ...] where z1 = f z0 x1; z2 = f z1 x2; ...

게으른 평가 덕분에 두 함수는 완전히 계산하지 않고 무한한 목록을 정의합니다. 즉, 우리는 fib 함수를 작성할 수 있습니다.이 함수는 무한 피보나치 시퀀스의 n 번째 요소를 검색합니다.

GHCi> let fib n = fibs !! n  -- (!!) being the list subscript operator
-- or in point-free style:
GHCi> let fib = (fibs !!)
GHCi> fib 9
34

시작하기

온라인 REPL

Haskell을 작성하는 가장 쉬운 방법은 Haskell 웹 사이트 또는 Haskell 을 방문하여 홈 페이지에서 온라인 REPL (read-eval-print-loop)을 사용하는 것입니다. 온라인 REPL은 대부분의 기본 기능과 일부 IO를 지원합니다. 명령 help 을 입력하여 시작할 수있는 기본 자습서도 있습니다. 하스켈의 기초를 배우기 시작하고 몇 가지 물건을 시험해보기위한 이상적인 도구.

GHC (i)

조금 더 참여할 준비가 된 프로그래머에게는 Glorious / Glasgow Haskell 컴파일러 와 함께 제공되는 대화 형 환경 인 GHCi 가 있습니다. GHC 는 별도로 설치할 수 있지만 이는 컴파일러에 불과합니다. 새 라이브러리를 설치할 수 있으려면 CabalStack 과 같은 도구도 설치해야합니다. Unix 계열 운영 체제를 사용하는 경우 가장 쉬운 방법은 다음을 사용하여 Stack 을 설치하는 것입니다.

curl -sSL https://get.haskellstack.org/ | sh

이렇게하면 나머지 시스템과 격리 된 GHC가 설치되므로 쉽게 제거 할 수 있습니다. 모든 명령 앞에 stack 해야합니다. 또 다른 간단한 접근법은 Haskell Platform 을 설치하는 것입니다. 플랫폼은 다음 두 가지 형태로 존재합니다.

  1. 최소한의 배포판에는 GHC (컴파일)와 Cabal / Stack (패키지 설치 및 빌드)
  2. 전체 배포판에는 프로젝트 개발, 프로파일 링 및 범위 분석을위한 도구가 추가로 포함되어 있습니다. 또한 널리 사용되는 패키지 세트가 추가로 포함되어 있습니다.

이러한 플랫폼은 설치 프로그램을 다운로드 하고 지침에 따라 또는 배포판의 패키지 관리자를 사용하여 설치할 수 있습니다 (이 버전은 최신 버전이 아닙니다).

  • 우분투, 데비안, 민트 :

    sudo apt-get install haskell-platform
    
  • 페도라 :

    sudo dnf install haskell-platform
    
  • 빨간 모자:

    sudo yum install haskell-platform
    
  • 아치 리눅스 :

    sudo pacman -S ghc cabal-install haskell-haddock-api \
                   haskell-haddock-library happy alex
    
  • 젠투 :

    sudo layman -a haskell
    sudo emerge haskell-platform
    
  • 자작과 OSX :

    brew cask install haskell-platform
    
  • MacPorts가있는 OSX :

    sudo port install haskell-platform
    

한번 설치되면, 호출하여 GHCi를 시작할 수 있어야한다 ghci 터미널 어디서나 명령을 사용합니다. 설치가 잘 되었다면 콘솔은 다음과 같이 보일 것입니다.

me@notebook:~$ ghci
GHCi, version 6.12.1: http://www.haskell.org/ghc/  :? for help
Prelude> 

아마도 Prelude> 이전에 어떤 라이브러리가로드되었는지에 대한 정보가있을 수 있습니다. 이제, 콘솔은 Haskell REPL이되었고 온라인 REPL처럼 Haskell 코드를 실행할 수 있습니다. 이 대화 형 환경을 종료하려면 다음을 입력 할 수 있습니다 :q 또는 :quit . GHCi 에서 사용할 수있는 명령에 대한 자세한 내용을 보려면 다음 을 입력 :? 시작 화면에 표시된대로.

같은 일을 계속해서 한 줄로 작성하는 것이 항상 실제적인 것은 아니기 때문에 파일에 하스켈 코드를 작성하는 것이 좋습니다. 이러한 파일은 일반적으로 .hs 확장자를 가지며 :l 또는 :load 사용하여 REPL에로드 할 수 있습니다.

앞에서 언급했듯이 GHCi 는 실제로 컴파일러 인 GHC 의 일부입니다. 이 컴파일러는 Haskell 코드가있는 .hs 파일을 실행중인 프로그램으로 변환하는 데 사용할 수 있습니다. .hs 파일에는 많은 함수가 포함될 수 있으므로 main 함수를 파일에 정의해야합니다. 이것이 프로그램의 출발점이 될 것입니다. test.hs 파일은 다음 명령으로 컴파일 할 수 있습니다.

ghc test.hs

에러가없고 main 함수가 올바르게 정의 되었다면 객체 파일과 실행 파일을 생성 할 것입니다.

고급 도구

  1. 이미 패키지 관리자로 언급되었지만 스택 은 완전히 다른 방식으로 하스켈 개발에 유용한 도구가 될 수 있습니다. 일단 설치되면

    • GHC 설치 (여러 버전)
    • 프로젝트 생성 및 스캐 폴딩
    • 의존성 관리
    • 프로젝트 구축 및 테스트
    • 벤치마킹
  2. IHaskell은 IPython을위한 haskell 커널 이며 markdown 및 수학 표기법과 (실행 가능한) 코드를 결합 할 수 있습니다.

소수

가장 현저한 변형 :

100 세 미만

import Data.List ( (\\) )

ps100 = ((([2..100] \\ [4,6..100]) \\ [6,9..100]) \\ [10,15..100]) \\ [14,21..100]

   -- = (((2:[3,5..100]) \\ [9,15..100]) \\ [25,35..100]) \\ [49,63..100]

   -- = (2:[3,5..100]) \\ ([9,15..100] ++ [25,35..100] ++ [49,63..100])

제한 없는

Eratosthenes의 체, data-ordlist 패키지 사용 :

import qualified Data.List.Ordered

ps   = 2 : _Y ((3:) . minus [5,7..] . unionAll . map (\p -> [p*p, p*p+2*p..]))

_Y g = g (_Y g)   -- = g (g (_Y g)) = g (g (g (g (...)))) = g . g . g . g . ...

전통적인

(차선 이하 시험 시브)

ps = sieve [2..]
     where
     sieve (x:xs) = [x] ++ sieve [y | y <- xs, rem y x > 0]

-- = map head ( iterate (\(x:xs) -> filter ((> 0).(`rem` x)) xs) [2..] )

최적의 시험 배분

ps = 2 : [n | n <- [3..], all ((> 0).rem n) $ takeWhile ((<= n).(^2)) ps]

-- = 2 : [n | n <- [3..], foldr (\p r-> p*p > n || (rem n p > 0 && r)) True ps]

과도기적

시련 부문에서 에라토스테네스의 체질까지 :

[n | n <- [2..], []==[i | i <- [2..n-1], j <- [0,i..n], j==n]]

최단 코드

nubBy (((>1).).gcd) [2..]          -- i.e., nubBy (\a b -> gcd a b > 1) [2..]

nubByData.List 에서 가져온 것입니다 (\\) .

값 선언하기

다음과 같이 REPL에서 일련의 표현식을 선언 할 수 있습니다.

Prelude> let x = 5
Prelude> let y = 2 * 5 + x
Prelude> let result = y * 10
Prelude> x
5
Prelude> y
15
Prelude> result
150

파일에 같은 값을 선언하기 위해 다음과 같이 작성합니다.

-- demo.hs

module Demo where
-- We declare the name of our module so 
-- it can be imported by name in a project.

x = 5

y = 2 * 5 + x

result = y * 10

모듈 이름은 변수 이름과 달리 대문자로 표기됩니다.