Haskell Language
일반적인 GHC 언어 확장
수색…
비고
이러한 언어 확장은 일반적으로 Glasgow Haskell 컴파일러 (GHC)를 사용할 때 승인 된 Haskell 2010 언어 보고서의 일부가 아니므로 사용할 수 있습니다. 이러한 확장을 사용하려면 플래그를 사용하여 컴파일러에 알리거나 파일의 module
키워드 앞에 LANGUAGE
programa를 두어야합니다. 공식 문서는 GCH 사용자 가이드의 7 절 에서 찾을 수 있습니다.
LANGUAGE
프로그램의 형식은 {-# LANGUAGE ExtensionOne, ExtensionTwo ... #-}
입니다. 그것은 리터럴 {-#
뒤에 LANGUAGE
뒤에 확장자가 쉼표로 구분 된 목록이오고 마지막으로 닫는 #-}
입니다. 여러 LANGUAGE
프로그램을 하나의 파일에 둘 수 있습니다.
MultiParamTypeClasses
여러 유형 매개 변수가있는 유형 클래스를 허용하는 매우 일반적인 확장입니다. MPTC는 유형 간의 관계로 생각할 수 있습니다.
{-# LANGUAGE MultiParamTypeClasses #-}
class Convertable a b where
convert :: a -> b
instance Convertable Int Float where
convert i = fromIntegral i
매개 변수 순서가 중요합니다.
MPTC는 때로는 유형 계열로 대체 될 수 있습니다.
FlexibleInstances
정규 인스턴스에는 다음이 필요합니다.
All instance types must be of the form (T a1 ... an)
where a1 ... an are *distinct type variables*,
and each type variable appears at most once in the instance head.
즉, 예를 들어 [a]
대한 인스턴스를 만들 수 있지만 [Int]
대한 인스턴스를 만들 수는 없습니다. FlexibleInstances
완화합니다.
class C a where
-- works out of the box
instance C [a] where
-- requires FlexibleInstances
instance C [Int] where
오버로드 된 문자열
일반적으로 Haskell의 문자열 리터럴은 String
유형을가집니다.이 String
은 [Char]
의 유형 별칭입니다. 소규모 교육 프로그램에서는 문제가되지 않지만 실제로는 Text
또는 ByteString
과 같은보다 효율적인 저장이 필요합니다.
OverloadedStrings
단순히 리터럴 유형을
"test" :: Data.String.IsString a => a
이러한 유형을 예상하는 함수에 직접 전달할 수 있습니다. 많은 라이브러리가 [Char]
보다 특정 시간 및 공간 이점을 제공하는 Data.Text 및 Data.ByteString 을 비롯한 문자열 유형에 대해이 인터페이스를 구현합니다.
또한 SQL 쿼리를 일반적인 문자열처럼 큰 따옴표로 쓰지 만 SQL 연결 공격의 악의적 인 소스 인 부적절한 연결에 대한 보호 기능을 제공하는 Postgresql-simple 라이브러리의 것과 같은 OverloadedStrings
고유 한 용도가 있습니다.
IsString
클래스의 인스턴스를 만들려면 fromString
함수를 구현해야합니다. 예 † :
data Foo = A | B | Other String deriving Show
instance IsString Foo where
fromString "A" = A
fromString "B" = B
fromString xs = Other xs
tests :: [ Foo ]
tests = [ "A", "B", "Testing" ]
† 이 예제에서는 Lyndon Maydwell (GitHub의 sordina
)이 여기 에서 찾았 습니다 .
TupleSections
섹션 방식으로 튜플 생성자 (연산자)를 적용 할 수있는 구문 확장입니다.
(a,b) == (,) a b
-- With TupleSections
(a,b) == (,) a b == (a,) b == (,b) a
N- 튜플
또한 2보다 큰 아티 (arity)를 갖는 튜플
(,2,) 1 3 == (1,2,3)
매핑
이것은 섹션이 사용되는 다른 장소에서 유용 할 수 있습니다 :
map (,"tag") [1,2,3] == [(1,"tag"), (2, "tag"), (3, "tag")]
이 확장명이없는 위의 예제는 다음과 같습니다.
map (\a -> (a, "tag")) [1,2,3]
UnicodeSyntax
특정 내장 연산자와 이름 대신 유니 코드 문자를 사용할 수있는 확장 프로그램입니다.
ASCII | 유니 코드 | 사용 |
---|---|---|
:: | ∷ | 유형이있다 |
-> | → | 함수 유형, 람다, case 브랜치 등 |
=> | ⇒ | 클래스 제약 |
forall | ∀ | 명백한 다형성 |
<- | ← | do 표기를 |
* | ★ | 유형의 종류 (또는 종류) (예 : Int :: ★ ) |
>- | ⤚ | Arrows 대한 proc 표기법 |
-< | ⤙ | Arrows 대한 proc 표기법 |
>>- | ⤜ | Arrows 대한 proc 표기법 |
-<< | ⤛ | Arrows 대한 proc 표기법 |
예 :
runST :: (forall s. ST s a) -> a
될 것이다
runST ∷ (∀ s. ST s a) → a
*
대 ★
예제는 약간 다릅니다. *
가 예약되어 있지 않으므로 ★
또한 곱셈에 대해 *
와 같은 방식으로 작동하거나 (*)
명명 된 다른 함수와 그 반대의 경우도 마찬가지입니다. 예 :
ghci> 2 ★ 3
6
ghci> let (*) = (+) in 2 ★ 3
5
ghci> let (★) = (-) in 2 * 3
-1
이진 리터럴
표준 하스켈은 정수 (접두어없이) 진수 리터럴 16 진수 (앞에 쓸 수 있습니다 0x
또는 0X
)와 진수 (앞에 0o
또는 0O
). BinaryLiterals
확장은 바이너리 옵션 ( 0b
또는 0B
앞에 붙임)을 추가합니다.
0b1111 == 15 -- evaluates to: True
ExistentialQuantification
이것은 단지 런타임 †에서 인스턴스화 형태 변수가, 존재 적 즉, 정량화, 또는되는 유형을 할 수있는 종류의 시스템 확장입니다.
실존 타입의 값은 객체 지향 언어의 abstract-base-class 레퍼런스와 유사합니다 : 당신은 contains의 정확한 타입을 알지 못하지만 타입의 클래스 를 제한 할 수 있습니다.
data S = forall a. Show a => S a
GADT 구문을 사용하면 다음과 같습니다.
{-# LANGUAGE GADTs #-}
data S where
S :: Show a => a -> S
존재 유형은 거의 이질적인 컨테이너와 같은 것들에 대한 문을 열어 준다 : 위에서 말했듯이, 실제로 S
값에는 다양한 유형이있을 수 있지만, 그것들 모두는 show
수 있으므로, 또한 할 수있다.
instance Show S where
show (S a) = show a -- we rely on (Show a) from the above
이제 이러한 객체들의 집합을 생성 할 수 있습니다 :
ss = [S 5, S "test", S 3.0]
또한 다형성 (polymorphic) 동작을 사용할 수 있습니다.
mapM_ print ss
Existentials는 매우 강력 할 수 있지만, Haskell에서는 실제로 필요하지 않습니다. 위 예제에서 Show
인스턴스로 실제로 할 수있는 것은 문자열 표현을 만드는 것입니다. 따라서 전체 S
유형은 표시 할 때 얻은 문자열만큼 정확하게 정보를 포함합니다. 따라서 일반적으로 문자열을 바로 저장하는 것이 좋습니다. 특히 하스켈이 게으르므로 문자열은 처음에는 평가되지 않은 썽크가됩니다.
반면에, 존재는 몇 가지 독특한 문제를 일으킨다. 예를 들어, 유형 정보가 실존 적으로 "숨겨진"방식. S
값을 패턴 매치하면 범위에 (더 정확하게는 Show
인스턴스에 포함 된) 유형이 포함되지만이 정보는 절대 범위를 벗어날 수 없으므로 컴파일러는 약간의 "비밀 사회"가됩니다. 외부에서 타입이 이미 알려진 값을 제외하고는 아무 것도 이스케이프하지 않습니다. 이것은 Couldn't match type 'a0' with '()' 'a0' is untouchable
이상한 오류가 발생할 Couldn't match type 'a0' with '()' 'a0' is untouchable
있습니다.
† 이것을 일반적인 매개 변수 다형성과 대조합니다.이 다형성은 일반적으로 컴파일시 해결됩니다 (전체 유형 삭제 허용).
Existential 타입은 Rank-N 타입과는 다르다.이 확장은 대략적으로 말하자면 서로 이중이다. 실존 타입의 값을 실제로 사용하기 위해서는 예제에서 show
와 같은 (아마도 제한된) 다형 함수가 필요하다. 다형 함수는 보편적으로 정량화됩니다. 즉, 주어진 클래스의 모든 유형에 대해 작동 합니다 . 반면에 실존 적 정량화는 선험적으로 알려지지 않은 특정 유형에 대해 작동 함을 의미합니다. 다형 함수가있는 경우 인수로 다형 함수를 전달하는 것으로 충분하지만 {-# LANGUAGE Rank2Types #-}
.
genShowSs :: (∀ x . Show x => x -> String) -> [S] -> [String]
genShowSs f = map (\(S a) -> f a)
람다 케이스
\arg -> case arg of
대신에 \arg -> case arg of
\case
를 쓸 수있는 구문 확장.
다음 함수 정의를 고려하십시오.
dayOfTheWeek :: Int -> String
dayOfTheWeek 0 = "Sunday"
dayOfTheWeek 1 = "Monday"
dayOfTheWeek 2 = "Tuesday"
dayOfTheWeek 3 = "Wednesday"
dayOfTheWeek 4 = "Thursday"
dayOfTheWeek 5 = "Friday"
dayOfTheWeek 6 = "Saturday"
함수 이름을 반복하지 않으려면 다음과 같이 작성할 수 있습니다.
dayOfTheWeek :: Int -> String
dayOfTheWeek i = case i of
0 -> "Sunday"
1 -> "Monday"
2 -> "Tuesday"
3 -> "Wednesday"
4 -> "Thursday"
5 -> "Friday"
6 -> "Saturday"
LambdaCase 확장을 사용하면 인수의 이름을 지정하지 않고 함수 표현식으로 작성할 수 있습니다.
{-# LANGUAGE LambdaCase #-}
dayOfTheWeek :: Int -> String
dayOfTheWeek = \case
0 -> "Sunday"
1 -> "Monday"
2 -> "Tuesday"
3 -> "Wednesday"
4 -> "Thursday"
5 -> "Friday"
6 -> "Saturday"
RankNTypes
다음 상황을 상상해보십시오.
foo :: Show a => (a -> String) -> String -> Int -> IO ()
foo show' string int = do
putStrLn (show' string)
putStrLn (show' int)
여기서는 값을 String으로 변환하고,이 함수를 문자열 매개 변수와 int 매개 변수에 모두 적용하고 둘 다 인쇄하는 함수를 전달하려고합니다. 내 마음 속에는 실패 할 이유가 없습니다! 우리는 전달되는 매개 변수의 두 가지 유형 모두에서 작동하는 함수를 가지고 있습니다.
불행히도, 이것은 유형 체크를하지 않을 것입니다! GHC는 함수 본문에서 첫 번째 발생을 기반으로 a
형식을 유추합니다. 즉, 우리가 명중하자마자 :
putStrLn (show' string)
GHC는 추측됩니다 show' :: String -> String
하기 때문에, string
A는 String
. show' int
를 show' int
하려고 시도하는 동안 폭발합니다.
RankNTypes
사용하면 다음과 같이 유형 서명을 작성하고 show'
유형을 충족시키는 모든 함수를 수량화 할 수 있습니다.
foo :: (forall a. Show a => (a -> String)) -> String -> Int -> IO ()
우리는 것을 주장하는이 계급이 다형성입니다 show'
기능은 모든 일을해야 우리의 함수 내에서의, 이전 구현이 작동합니다. a
RankNTypes
확장은 형식 서명에서 forall ...
블록을 임의로 중첩 할 수있게합니다. 즉, N 랭크의 다형성을 허용합니다.
오버로드 목록
GHC 7.8에 추가됨 .
유사 OverloadedLists, OverloadedStrings는 다음과 같이 목록 리터럴이 desugared 할 수 있습니다 :
[] -- fromListN 0 []
[x] -- fromListN 1 (x : [])
[x .. ] -- fromList (enumFrom x)
이것은 Set
, Vector
및 Map
과 같은 유형을 처리 할 때 편리합니다.
['0' .. '9'] :: Set Char
[1 .. 10] :: Vector Int
[("default",0), (k1,v1)] :: Map String Int
['a' .. 'z'] :: Text
IsList
의 클래스 GHC.Exts
이 확장과 함께 사용하기위한 것입니다.
IsList
에는 하나의 유형 함수 Item
과 세 개의 함수, 즉 fromList :: [Item l] -> l
, toList :: l -> [Item l]
및 fromListN :: Int -> [Item l] -> l
. fromListN
은 선택 사항입니다. 일반적인 구현은 다음과 같습니다.
instance IsList [a] where
type Item [a] = a
fromList = id
toList = id
instance (Ord a) => IsList (Set a) where
type Item (Set a) = a
fromList = Set.fromList
toList = Set.toList
기능적 종속성
인수가 a, b, c 및 x 인 다중 매개 변수 유형 클래스가있는 경우이 확장을 사용하면 유형 x가 a, b 및 c에서 고유하게 식별 될 수 있음을 나타낼 수 있습니다.
class SomeClass a b c x | a b c -> x where ...
이러한 클래스의 인스턴스를 선언 할 때 다른 모든 인스턴스와 비교하여 함수 종속성이 유지되는지 확인합니다. 즉 abc
같지만 x
가 다른 인스턴스는 존재하지 않습니다.
여러 종속성을 쉼표로 구분 된 목록으로 지정할 수 있습니다.
class OtherClass a b c d | a b -> c d, a d -> b where ...
예를 들어 MTL에서는 다음을 볼 수 있습니다.
class MonadReader r m| m -> r where ...
instance MonadReader r ((->) r) where ...
이제 MonadReader a ((->) Foo) => a
의 값을 가진다면 컴파일러는 a ~ Foo
유추 할 수 있습니다. 두 번째 인수가 첫 번째 인수를 완전히 결정하므로 그에 따라 형식을 단순화합니다.
SomeClass
클래스는 x
를 결과로하는 인수 abc
의 함수로 생각할 수 있습니다. 이러한 클래스는 타입 시스템에서 계산을 수행하는 데 사용될 수 있습니다.
GADT
기존의 대수 데이터 유형은 유형 변수에서 매개 변수입니다. 예를 들어, ADT를 정의하면
data Expr a = IntLit Int
| BoolLit Bool
| If (Expr Bool) (Expr a) (Expr a)
이 정적이 아닌 잘 입력 조건문을 배제 것이라는 희망으로,이 유형 때문에 예상대로 작동하지 않습니다 IntLit :: Int -> Expr a
의 선택을 위해 : universially 정량화 ,이 유형의 값을 생성 a
Expr a
. 특히 a ~ Bool
경우 IntLit :: Int -> Expr Bool
을 사용하여 If (IntLit 1) e1 e2
와 같은 것을 만들 수 있습니다. If
생성자는 If
생성자의 형식을 배제하려고 시도했습니다.
Generalized Algebraic Data Types를 사용하면 데이터 생성자의 결과 유형을 매개 변수가 아닌 단순한 형식으로 제어 할 수 있습니다. Expr
유형을 GADT로 다시 작성할 수 있습니다.
data Expr a where
IntLit :: Int -> Expr Int
BoolLit :: Bool -> Expr Bool
If :: Expr Bool -> Expr a -> Expr a -> Expr a
여기서 IntLit
생성자의 IntLit
은 Int -> Expr Int
이므로 IntLit 1 :: Expr Bool
은 형식 검사를하지 않습니다.
GADT 값의 패턴 일치는 반환되는 용어의 유형을 상세하게 만듭니다. 예를 들어, 다음과 같이 Expr a
대한 평가 Expr a
작성할 수 있습니다.
crazyEval :: Expr a -> a
crazyEval (IntLit x) =
-- Here we can use `(+)` because x :: Int
x + 1
crazyEval (BoolLit b) =
-- Here we can use `not` because b :: Bool
not b
crazyEval (If b thn els) =
-- Because b :: Expr Bool, we can use `crazyEval b :: Bool`.
-- Also, because thn :: Expr a and els :: Expr a, we can pass either to
-- the recursive call to `crazyEval` and get an a back
crazyEval $ if crazyEval b then thn else els
예를 들어, IntLit x
가 패턴 일치 인 a ~ Int
(또한 a ~ Bool
때 not
및 if_then_else_
대해서도)를 배우기 때문에 위의 정의에서 (+)
를 사용할 수 있습니다.
ScopedTypeVariables
ScopedTypeVariables
를 사용하면 선언 내에서 보편적으로 정량화 된 유형을 참조 할 수 있습니다. 더 명백하게하기 위해서 :
import Data.Monoid
foo :: forall a b c. (Monoid b, Monoid c) => (a, b, c) -> (b, c) -> (a, b, c)
foo (a, b, c) (b', c') = (a :: a, b'', c'')
where (b'', c'') = (b <> b', c <> c') :: (b, c)
중요한 것은 a
, b
및 c
를 사용하여 선언의 하위 표현 ( where
절의 튜플과 최종 결과의 첫 번째 a
에서 컴파일러에 지시 할 수 있다는 것입니다. 실제로 ScopedTypeVariables
는 복잡한 함수를 파트의 합으로 작성하는 것을 도와 주므로 프로그래머는 구체 유형이없는 중간 값에 유형 시그니처를 추가 할 수 있습니다.
PatternSynonyms
패턴 동의어 는 함수가 표현식을 추상화하는 것과 유사한 패턴의 추상화입니다.
이 예제에서는 Data.Sequence
인터페이스의 인터페이스를 살펴보고 패턴 동의어를 사용하여 데이터를 향상시킬 수있는 방법을 살펴 보겠습니다. Seq
유형은 내부적으로 다양한 작업, 특히 O (1) (un) consing 및 (un) snocing 모두에 대해 좋은 점근 적 복잡성을 달성하기 위해 내부적으로 복잡한 표현 을 사용하는 데이터 유형입니다.
그러나이 표현은 다루기 힘들고 그 불변량의 일부는 하스켈의 형식 체계에서 표현 될 수 없다. 이 때문에 Seq
유형은 불변 보존 접근 자 및 생성자 함수와 함께 추상적 유형으로 사용자에게 노출됩니다.
empty :: Seq a
(<|) :: a -> Seq a -> Seq a
data ViewL a = EmptyL | a :< (Seq a)
viewl :: Seq a -> ViewL a
(|>) :: Seq a -> a -> Seq a
data ViewR a = EmptyR | (Seq a) :> a
viewr :: Seq a -> ViewR a
그러나이 인터페이스를 사용하는 것은 약간 번거로운 일이 될 수 있습니다.
uncons :: Seq a -> Maybe (a, Seq a)
uncons xs = case viewl xs of
x :< xs' -> Just (x, xs')
EmptyL -> Nothing
보기 패턴 을 사용하여 다소 정리할 수 있습니다.
{-# LANGUAGE ViewPatterns #-}
uncons :: Seq a -> Maybe (a, Seq a)
uncons (viewl -> x :< xs) = Just (x, xs)
uncons _ = Nothing
PatternSynonyms
언어 확장을 사용하면 패턴 매칭을 통해 우리가 생각 나게하는 snoc-list가 있다고 가정하여 더 멋진 인터페이스를 제공 할 수 있습니다.
{-# LANGUAGE PatternSynonyms #-}
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq
pattern Empty :: Seq a
pattern Empty <- (Seq.viewl -> Seq.EmptyL)
pattern (:<) :: a -> Seq a -> Seq a
pattern x :< xs <- (Seq.viewl -> x Seq.:< xs)
pattern (:>) :: Seq a -> a -> Seq a
pattern xs :> x <- (Seq.viewr -> xs Seq.:> x)
이것은 우리가 아주 자연스러운 스타일로 uncons
을 쓸 수있게 해줍니다 :
uncons :: Seq a -> Maybe (a, Seq a)
uncons (x :< xs) = Just (x, xs)
uncons _ = Nothing
RecordWildCards
RecordWildCards를 참조하십시오 .