표준 Prelude
표준 Prelude (Standard Prelude)
이 장에서는 Haskell Prelude 전체를 제시해요. 이 장 자체가 Prelude에 대한 명세(specification)가 돼요. 많은 정의가 효율성보다 명확성(clarity)을 염두에 두고 작성됐으며, 이 명세가 여기 보이는 대로 구현될 필요는 없어요.
본문
클래스 선언과 함께 주어지는 기본 메서드 정의는 오직 기본 메서드(default method)에 대한 명세일 뿐이에요. 그것이 모든 인스턴스에서 메서드의 의미를 명세하는 것은 아니에요. 예를 들어 클래스 Enum의 enumFrom에 대한 기본 메서드는 범위가 Int를 넘어서는 타입에서는 제대로 동작하지 않아요. fromEnum이 그 타입의 모든 값을 서로 다른 Int 값으로 매핑할 수 없기 때문이에요.
여기에 보이는 Prelude는 루트 모듈 Prelude와 세 개의 하위 모듈인 PreludeList, PreludeText, PreludeIO로 구성돼요. 이 구조는 순전히 표기상의 것(구성)이에요. 구현이 이 구성을 써야 할 의무는 없고, 이 세 모듈을 따로따로 import할 수도 없어요. 오직 모듈 Prelude의 내보내기(export)만이 의미가 있어요.
이 모듈 중 일부는 Data.Char, Control.Monad, System.IO, Numeric 같은 라이브러리 모듈을 import해요. 이 모듈들은 2부에서 완전히 다뤄져요. 물론 이런 import는 Prelude 명세의 일부가 아니에요. 즉 구현은 라이브러리 모듈을 원하는 만큼 더 또는 덜 import할 수 있어요.
prim으로 시작하는 이름으로 표시되는, Haskell로 정의할 수 없는 원시 연산(primitives)은 모듈 PreludeBuiltin에 시스템 의존적인 방식으로 정의되며 여기에서는 보이지 않아요. 원시를 클래스 메서드에 그저 묶어놓은 인스턴스 선언은 생략돼요. 명백한 기능을 가진, 더 장황한 인스턴스 중 일부는 간결함을 위해 생략됐어요.
Integer나 () 같은 특별한 타입에 대한 선언은 완전성을 위해 Prelude에 포함돼 있지만, 그 선언은 불완전하거나 문법적으로 유효하지 않을 수 있어요. 정의의 나머지를 Haskell로 표현할 수 없는 곳에는 타원 ...이 자주 쓰여요.
뜻밖의 모호성 오류를 줄이고 효율을 높이기 위해, 리스트에 대해 널리 쓰이는 많은 함수는 Integral a나 Num a 같은 더 일반적인 숫자 타입 대신 Int 타입을 사용해요. 그런 함수들은 take, drop, !!, length, splitAt, replicate이에요. 더 일반화된 버전은 Data.List 라이브러리에 generic 접두사를 붙여 주어져요. 예를 들어 genericLength예요.
루트 모듈 Prelude
전체 내보내기 목록과 루트 모듈 Prelude의 구현은 다음과 같아요.
module Prelude (
module PreludeList, module PreludeText, module PreludeIO,
Bool(False, True),
Maybe(Nothing, Just),
Either(Left, Right),
Ordering(LT, EQ, GT),
Char, String, Int, Integer, Float, Double, Rational, IO,
-- These built-in types are defined in the Prelude, but
-- are denoted by built-in syntax, and cannot legally
-- appear in an export list.
-- List type: []((:), [])
-- Tuple types: (,)((,)), (,,)((,,)), etc.
-- Trivial type: ()(())
-- Functions: (->)
Eq((==), (/=)),
Ord(compare, (<), (<=), (>=), (>), max, min),
Enum(succ, pred, toEnum, fromEnum, enumFrom, enumFromThen,
enumFromTo, enumFromThenTo),
Bounded(minBound, maxBound),
Num((+), (-), (⋆), negate, abs, signum, fromInteger),
Real(toRational),
Integral(quot, rem, div, mod, quotRem, divMod, toInteger),
Fractional((/), recip, fromRational),
Floating(pi, exp, log, sqrt, (⋆⋆), logBase, sin, cos, tan,
asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh),
RealFrac(properFraction, truncate, round, ceiling, floor),
RealFloat(floatRadix, floatDigits, floatRange, decodeFloat,
encodeFloat, exponent, significand, scaleFloat, isNaN,
isInfinite, isDenormalized, isIEEE, isNegativeZero, atan2),
Monad((>>=), (>>), return, fail),
Functor(fmap),
mapM, mapM_, sequence, sequence_, (=<<),
maybe, either,
(&&), (||), not, otherwise,
subtract, even, odd, gcd, lcm, (^), (^^),
fromIntegral, realToFrac,
fst, snd, curry, uncurry, id, const, (.), flip, ($), until,
asTypeOf, error, undefined,
seq, ($!)
) where
import PreludeBuiltin -- Contains all ‘prim' values
import UnicodePrims( primUnicodeMaxChar ) -- Unicode primitives
import PreludeList
import PreludeText
import PreludeIO
import Data.Ratio( Rational )
infixr 9 .
infixr 8 ^, ^^, ⋆⋆
infixl 7 ⋆, /, ‘quot‘, ‘rem‘, ‘div‘, ‘mod‘
infixl 6 +, -
-- The (:) operator is built-in syntax, and cannot legally be given
-- a fixity declaration; but its fixity is given by:
-- infixr 5 :
infix 4 ==, /=, <, <=, >=, >
infixr 3 &&
infixr 2 ||
infixl 1 >>, >>=
infixr 1 =<<
infixr 0 $, $!, ‘seq‘
표준 타입·클래스·인스턴스와 관련 함수
-- Equality and Ordered classes
class Eq a where
(==), (/=) :: a -> a -> Bool
-- Minimal complete definition:
-- (==) or (/=)
x /= y = not (x == y)
x == y = not (x /= y)
class (Eq a) => Ord a where
compare :: a -> a -> Ordering
(<), (<=), (>=), (>) :: a -> a -> Bool
max, min :: a -> a -> a
-- Minimal complete definition:
-- (<=) or compare
-- Using compare can be more efficient for complex types.
compare x y
| x == y = EQ
| x <= y = LT
| otherwise = GT
x <= y = compare x y /= GT
x < y = compare x y == LT
x >= y = compare x y /= LT
x > y = compare x y == GT
-- note that (min x y, max x y) = (x,y) or (y,x)
max x y
| x <= y = y
| otherwise = x
min x y
| x <= y = x
| otherwise = y
Eq는 동등성(equality)을, Ord는 순서(order)를 다루는 클래스예요. Eq의 (==)와 (/=)는 서로를 통해 기본 구현이 순환 정의되는데, 두 메서드 중 하나만 최소 정의로 주면 나머지는 자동으로 채워져요. Ord는 compare가 EQ/LT/GT를 돌려주고, 비교 연산자와 max·min이 그로부터 파생돼요.
-- Enumeration and Bounded classes
class Enum a where
succ, pred :: a -> a
toEnum :: Int -> a
fromEnum :: a -> Int
enumFrom :: a -> [a] -- [n..]
enumFromThen :: a -> a -> [a] -- [n,n'..]
enumFromTo :: a -> a -> [a] -- [n..m]
enumFromThenTo :: a -> a -> a -> [a] -- [n,n'..m]
-- Minimal complete definition:
-- toEnum, fromEnum
--
-- NOTE: these default methods only make sense for types
-- that map injectively into Int using fromEnum
-- and toEnum.
succ = toEnum . (+1) . fromEnum
pred = toEnum . (subtract 1) . fromEnum
enumFrom x = map toEnum [fromEnum x ..]
enumFromTo x y = map toEnum [fromEnum x .. fromEnum y]
enumFromThen x y = map toEnum [fromEnum x, fromEnum y ..]
enumFromThenTo x y z =
map toEnum [fromEnum x, fromEnum y .. fromEnum z]
class Bounded a where
minBound :: a
maxBound :: a
Enum은 서수(ordinal) 타입을 위한 클래스로, succ·pred와 산술 수열 생성을 지원해요. toEnum/fromEnum이 Int와의 단사 매핑을 제공한다는 전제 아래 기본 메서드들이 구현돼요. Bounded는 타입의 최소·최대 원소인 minBound/maxBound를 정의해요.
숫자 클래스
-- Numeric classes
class (Eq a, Show a) => Num a where
(+), (-), (⋆) :: a -> a -> a
negate :: a -> a
abs, signum :: a -> a
fromInteger :: Integer -> a
-- Minimal complete definition:
-- All, except negate or (-)
x - y = x + negate y
negate x = 0 - x
class (Num a, Ord a) => Real a where
toRational :: a -> Rational
class (Real a, Enum a) => Integral a where
quot, rem :: a -> a -> a
div, mod :: a -> a -> a
quotRem, divMod :: a -> a -> (a,a)
toInteger :: a -> Integer
-- Minimal complete definition:
-- quotRem, toInteger
n ‘quot‘ d = q where (q,r) = quotRem n d
n ‘rem‘ d = r where (q,r) = quotRem n d
n ‘div‘ d = q where (q,r) = divMod n d
n ‘mod‘ d = r where (q,r) = divMod n d
divMod n d = if signum r == - signum d then (q-1, r+d) else qr
where qr@(q,r) = quotRem n d
class (Num a) => Fractional a where
(/) :: a -> a -> a
recip :: a -> a
fromRational :: Rational -> a
-- Minimal complete definition:
-- fromRational and (recip or (/))
recip x = 1 / x
x / y = x ⋆ recip y
class (Fractional a) => Floating a where
pi :: a
exp, log, sqrt :: a -> a
(⋆⋆), logBase :: a -> a -> a
sin, cos, tan :: a -> a
asin, acos, atan :: a -> a
sinh, cosh, tanh :: a -> a
asinh, acosh, atanh :: a -> a
-- Minimal complete definition:
-- pi, exp, log, sin, cos, sinh, cosh
-- asin, acos, atan
-- asinh, acosh, atanh
x ⋆⋆ y = exp (log x ⋆ y)
logBase x y = log y / log x
sqrt x = x ⋆⋆ 0.5
tan x = sin x / cos x
tanh x = sinh x / cosh x
class (Real a, Fractional a) => RealFrac a where
properFraction :: (Integral b) => a -> (b,a)
truncate, round :: (Integral b) => a -> b
ceiling, floor :: (Integral b) => a -> b
-- Minimal complete definition:
-- properFraction
truncate x = m where (m,_) = properFraction x
round x = let (n,r) = properFraction x
m = if r < 0 then n - 1 else n + 1
in case signum (abs r - 0.5) of
-1 -> n
0 -> if even n then n else m
1 -> m
ceiling x = if r > 0 then n + 1 else n
where (n,r) = properFraction x
floor x = if r < 0 then n - 1 else n
where (n,r) = properFraction x
class (RealFrac a, Floating a) => RealFloat a where
floatRadix :: a -> Integer
floatDigits :: a -> Int
floatRange :: a -> (Int,Int)
decodeFloat :: a -> (Integer,Int)
encodeFloat :: Integer -> Int -> a
exponent :: a -> Int
significand :: a -> a
scaleFloat :: Int -> a -> a
isNaN, isInfinite, isDenormalized, isNegativeZero, isIEEE
:: a -> Bool
atan2 :: a -> a -> a
-- Minimal complete definition:
-- All except exponent, significand,
-- scaleFloat, atan2
exponent x = if m == 0 then 0 else n + floatDigits x
where (m,n) = decodeFloat x
significand x = encodeFloat m (- floatDigits x)
where (m,_) = decodeFloat x
scaleFloat k x = encodeFloat m (n+k)
where (m,n) = decodeFloat x
atan2 y x
| x>0 = atan (y/x)
| x==0 && y>0 = pi/2
| x<0 && y>0 = pi + atan (y/x)
|(x<=0 && y<0) ||
(x<0 && isNegativeZero y) ||
(isNegativeZero x && isNegativeZero y)
= -atan2 (-y) x
| y==0 && (x<0 || isNegativeZero x)
= pi -- must be after the previous test on zero y
| x==0 && y==0 = y -- must be after the other double zero tests
| otherwise = x + y -- x or y is a NaN, return a NaN (via +)
Num은 덧셈·뺄셈·곱셈, 부호 반전(negate), 절댓값(abs), 부호(signum), 그리고 정수에서의 변환 fromInteger를 제공하는 기본 숫자 클래스예요. Real은 유리수로의 변환 toRational을 추가해요. Integral은 몫·나머지(quot, rem, div, mod)와 그 쌍(quotRem, divMod), 그리고 toInteger를 정의해요 — 이때 div/mod는 나눗셈 결과를 -∞ 쪽으로(-infinity 방향으로) 반올림하고, quot/rem은 0 쪽으로 반올림한다는 차이를 기본 정의에 반영해요. Fractional은 나눗셈과 역수, 유리수에서의 변환을 다뤄요. Floating은 지수·로그·삼각·쌍곡 함수를 제공해요. RealFrac은 properFraction을 통해 실수의 정수·소수 부분을 분리하고 여기서 truncate, round, ceiling, floor를 만들어내요. RealFloat은 부동소수점 내부 표현(floatRadix, floatDigits, floatRange, decodeFloat, encodeFloat, exponent, significand, scaleFloat)과 특수 값 검사(isNaN, isInfinite, isDenormalized, isNegativeZero, isIEEE) 및 사분면 정확한 atan2를 제공해요.
숫자 함수
-- Numeric functions
subtract :: (Num a) => a -> a -> a
subtract = flip (-)
even, odd :: (Integral a) => a -> Bool
even n = n ‘rem‘ 2 == 0
odd = not . even
gcd :: (Integral a) => a -> a -> a
gcd 0 0 = error "Prelude.gcd: gcd 0 0 is undefined"
gcd x y = gcd' (abs x) (abs y)
where gcd' x 0 = x
gcd' x y = gcd' y (x ‘rem‘ y)
lcm :: (Integral a) => a -> a -> a
lcm _ 0 = 0
lcm 0 _ = 0
lcm x y = abs ((x ‘quot‘ (gcd x y)) ⋆ y)
(^) :: (Num a, Integral b) => a -> b -> a
x ^ 0 = 1
x ^ n | n > 0 = f x (n-1) x
where f _ 0 y = y
f x n y = g x n where
g x n | even n = g (x⋆x) (n ‘quot‘ 2)
| otherwise = f x (n-1) (x⋆y)
_ ^ _ = error "Prelude.^: negative exponent"
(^^) :: (Fractional a, Integral b) => a -> b -> a
x ^^ n = if n >= 0 then x^n else recip (x^(-n))
fromIntegral :: (Integral a, Num b) => a -> b
fromIntegral = fromInteger . toInteger
realToFrac :: (Real a, Fractional b) => a -> b
realToFrac = fromRational . toRational
subtract는 뺄셈의 인자 순서를 뒤집은 함수예요. even/odd는 짝수·홀수 판정이고, gcd는 유클리드 호제법으로 최대공약수를, lcm은 최소공배수를 계산해요. (^)는 비음인 정수 지수에 대해 이진 거듭제곱으로, (^^)는 음의 지수일 때 역수를 취하는 방식으로 동작해요. fromIntegral은 Integral 타입을 임의의 Num 타입으로, realToFrac은 Real 타입을 임의의 Fractional 타입으로 변환해요.
모나드 클래스
-- Monadic classes
class Functor f where
fmap :: (a -> b) -> f a -> f b
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
-- Minimal complete definition:
-- (>>=), return
m >> k = m >>= \_ -> k
fail s = error s
sequence :: Monad m => [m a] -> m [a]
sequence = foldr mcons (return [])
where mcons p q = p >>= \x -> q >>= \y -> return (x:y)
sequence_ :: Monad m => [m a] -> m ()
sequence_ = foldr (>>) (return ())
-- The xxxM functions take list arguments, but lift the function or
-- list element to a monad type
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM f as = sequence (map f as)
mapM_ :: Monad m => (a -> m b) -> [a] -> m ()
mapM_ f as = sequence_ (map f as)
(=<<) :: Monad m => (a -> m b) -> m a -> m b
f =<< x = x >>= f
Functor는 구조를 보존하는 매핑 fmap을 제공하고, Monad는 순차 결합 (>>=), 결과 버림 (>>), 값 승격 return, 실패 처리 fail을 정의해요. 기본 정의에서 (>>)는 (>>=)를 통해, fail은 error를 통해 주어져요. sequence/sequence_는 모나딕 액션의 리스트를 순서대로 실행하고, mapM/mapM_은 리스트에 함수를 매핑한 뒤 sequence를 적용해요.
사소한(Trivial) 타입, 함수 타입과 기본 함수들
-- Trivial type
data () = () deriving (Eq, Ord, Enum, Bounded)
-- Not legal Haskell; for illustration only
-- Function type
-- identity function
id :: a -> a
id x = x
-- constant function
const :: a -> b -> a
const x _ = x
-- function composition
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \ x -> f (g x)
-- flip f takes its (first) two arguments in the reverse order of f.
flip :: (a -> b -> c) -> b -> a -> c
flip f x y = f y x
seq :: a -> b -> b
seq = ... -- Primitive
-- right-associating infix application operators
-- (useful in continuation-passing style)
($), ($!) :: (a -> b) -> a -> b
f $ x = f x
f $! x = x ‘seq‘ f x
()는 원소가 하나뿐인 유닛 타입이고, id는 항등 함수, const는 상수 함수, (.)는 함수 합성, flip은 인자 순서를 뒤집어요. seq는 인자 강제 평가(evaluation to WHNF)를 위한 원시 연산이에요. ($)는 낮은 우선순위의 오른쪽 결합 적용 연산자이고, ($!)는 인자를 강제로 평가한 뒤 적용해요.
부울 타입과 함수
-- Boolean type
data Bool = False | True deriving (Eq, Ord, Enum, Read, Show, Bounded)
-- Boolean functions
(&&), (||) :: Bool -> Bool -> Bool
True && x = x
False && _ = False
True || _ = True
False || x = x
not :: Bool -> Bool
not True = False
not False = True
otherwise :: Bool
otherwise = True
Bool은 False/True 두 생성자를 가진 타입이에요. (&&)와 (||)는 각각 정의에 따라 왼쪽 인자가 결과를 결정하면 오른쪽을 평가하지 않는 단락(short-circuit) 동작을 해요. not은 부정, otherwise는 항상 True인 값으로 가드 기본절에서 관용적으로 써요.
문자 타입과 문자열
-- Character type
data Char = ... 'a' | 'b' ... -- Unicode values
instance Eq Char where
c == c' = fromEnum c == fromEnum c'
instance Ord Char where
c <= c' = fromEnum c <= fromEnum c'
instance Enum Char where
toEnum = primIntToChar
fromEnum = primCharToInt
enumFrom c = map toEnum [fromEnum c .. fromEnum (maxBound::Char)]
enumFromThen c c' = map toEnum [fromEnum c, fromEnum c' .. fromEnum lastChar]
where lastChar :: Char
lastChar | c' < c = minBound
| otherwise = maxBound
instance Bounded Char where
minBound = '\0'
maxBound = primUnicodeMaxChar
type String = [Char]
Char은 유니코드 문자 타입으로, Eq·Ord·Enum·Bounded 인스턴스가 fromEnum/toEnum(정수 코드 변환)을 통해 정의돼요. enumFrom/enumFromThen은 문자 산술 수열을 만들고, minBound는 '\0', maxBound는 최대 유니코드 문자예요. String은 [Char]의 타입 동의어예요.
Maybe 타입
-- Maybe type
data Maybe a = Nothing | Just a deriving (Eq, Ord, Read, Show)
maybe :: b -> (a -> b) -> Maybe a -> b
maybe n f Nothing = n
maybe n f (Just x) = f x
instance Functor Maybe where
fmap f Nothing = Nothing
fmap f (Just x) = Just (f x)
instance Monad Maybe where
(Just x) >>= k = k x
Nothing >>= k = Nothing
return = Just
fail s = Nothing
Maybe a는 값이 없음(Nothing)이나 값이 있음(Just a)을 나타내는 타입이에요. maybe는 기본값과 함수로 Maybe를 처리하는 접기(fold) 함수예요. Functor 인스턴스는 안쪽 값에 fmap을 적용하고, Monad 인스턴스는 Nothing을 전파(short-circuit)하며 fail은 Nothing을 돌려줘요.
Either 타입
-- Either type
data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)
either :: (a -> c) -> (b -> c) -> Either a b -> c
either f g (Left x) = f x
either f g (Right y) = g y
Either a b는 두 타입 중 하나의 값을 담는 타입으로, 보통 오류(Left)와 성공(Right)을 나타내는 데 써요. either는 두 처리 함수로 Left/Right를 접는 함수예요.
IO 타입
-- IO type
data IO a = ... -- abstract
instance Functor IO where
fmap f x = x >>= (return . f)
instance Monad IO where
(>>=) = ...
return = ...
fail s = ioError (userError s)
IO a는 입출력 액션을 나타내는 추상 타입이에요. Functor 인스턴스는 액션 결과에 함수를 적용하고, Monad 인스턴스는 액션의 순차 결합을 제공하며 fail은 ioError (userError s)로 예외를 일으켜요.
Ordering 타입
-- Ordering type
data Ordering = LT | EQ | GT
deriving (Eq, Ord, Enum, Read, Show, Bounded)
Ordering은 비교 결과를 나타내는 타입으로 LT(less than), EQ(equal), GT(greater than) 세 생성자를 가져요.
표준 숫자 타입
-- Standard numeric types. The data declarations for these types cannot
-- be expressed directly in Haskell since the constructor lists would be
-- far too large.
data Int = minBound ... -1 | 0 | 1 ... maxBound
instance Eq Int where ...
instance Ord Int where ...
instance Num Int where ...
instance Real Int where ...
instance Integral Int where ...
instance Enum Int where ...
instance Bounded Int where ...
data Integer = ... -1 | 0 | 1 ...
instance Eq Integer where ...
instance Ord Integer where ...
instance Num Integer where ...
instance Real Integer where ...
instance Integral Integer where ...
instance Enum Integer where ...
data Float
instance Eq Float where ...
instance Ord Float where ...
instance Num Float where ...
instance Real Float where ...
instance Fractional Float where ...
instance Floating Float where ...
instance RealFrac Float where ...
instance RealFloat Float where ...
data Double
instance Eq Double where ...
instance Ord Double where ...
instance Num Double where ...
instance Real Double where ...
instance Fractional Double where ...
instance Floating Double where ...
instance RealFrac Double where ...
instance RealFloat Double where ...
Int는 유계(bounded) 고정 정밀도 정수, Integer는 무제한 가변 정밀도 정수예요. Float는 단정밀도, Double은 배정밀도 IEEE 부동소수점 타입이에요. 이 타입들의 data 선언은 생성자 목록이 너무 커서 Haskell로 직접 표현할 수 없기 때문에 여기에서 완전히 펼치지 않아요.
-- The Enum instances for Floats and Doubles are slightly unusual.
-- The ‘toEnum' function truncates numbers to Int. The definitions
-- of enumFrom and enumFromThen allow floats to be used in arithmetic
-- series: [0,0.1 .. 0.95]. However, roundoff errors make these somewhat
-- dubious. This example may have either 10 or 11 elements, depending on
-- how 0.1 is represented.
instance Enum Float where
succ x = x+1
pred x = x-1
toEnum = fromIntegral
fromEnum = fromInteger . truncate -- may overflow
enumFrom = numericEnumFrom
enumFromThen = numericEnumFromThen
enumFromTo = numericEnumFromTo
enumFromThenTo = numericEnumFromThenTo
instance Enum Double where
succ x = x+1
pred x = x-1
toEnum = fromIntegral
fromEnum = fromInteger . truncate -- may overflow
enumFrom = numericEnumFrom
enumFromThen = numericEnumFromThen
enumFromTo = numericEnumFromTo
enumFromThenTo = numericEnumFromThenTo
numericEnumFrom :: (Fractional a) => a -> [a]
numericEnumFromThen :: (Fractional a) => a -> a -> [a]
numericEnumFromTo :: (Fractional a, Ord a) => a -> a -> [a]
numericEnumFromThenTo :: (Fractional a, Ord a) => a -> a -> a -> [a]
numericEnumFrom = iterate (+1)
numericEnumFromThen n m = iterate (+(m-n)) n
numericEnumFromTo n m = takeWhile (<= m+1/2) (numericEnumFrom n)
numericEnumFromThenTo n n' m = takeWhile p (numericEnumFromThen n n')
where
p | n' >= n = (<= m + (n'-n)/2)
| otherwise = (>= m + (n'-n)/2)
Float/Double의 Enum 인스턴스는 toEnum이 fromIntegral(정수로 잘라냄)을 사용하고 enumFrom 계열은 numericEnumFrom 계열을 사용해요. 덕분에 [0,0.1 .. 0.95] 같은 소수 산술 수열을 쓸 수 있지만, 반올림 오차 때문에 그런 수열은 다소 불확실할 수 있어요.
리스트 타입
-- Lists
data [a] = [] | a : [a] deriving (Eq, Ord)
-- Not legal Haskell; for illustration only
instance Functor [] where
fmap = map
instance Monad [] where
m >>= k = concat (map k m)
return x = [x]
fail s = []
리스트 타입 [a]는 빈 리스트 []와 앞에 원소를 붙이는 (:)로 구성돼요. Functor 인스턴스는 map, Monad 인스턴스는 리스트 모나드(각 원소에 함수를 적용해 결과를 이어붙이는 concat . map)를 제공해요. fail은 빈 리스트를 돌려줘요.
튜플 타입과 선택 함수
-- Tuples
data (a,b) = (a,b) deriving (Eq, Ord, Bounded)
data (a,b,c) = (a,b,c) deriving (Eq, Ord, Bounded)
-- Not legal Haskell; for illustration only
-- component projections for pairs:
-- (NB: not provided for triples, quadruples, etc.)
fst :: (a,b) -> a
fst (x,y) = x
snd :: (a,b) -> b
snd (x,y) = y
-- curry converts an uncurried function to a curried function;
-- uncurry converts a curried function to a function on pairs.
curry :: ((a, b) -> c) -> a -> b -> c
curry f x y = f (x, y)
uncurry :: (a -> b -> c) -> ((a, b) -> c)
uncurry f p = f (fst p) (snd p)
튜플 타입은 2-튜플 (a,b)와 3-튜플 (a,b,c)가 예시로 정의되어 있고 이보다 큰 튜플도 유사해요. fst/snd는 쌍의 첫·둘째 성분을 꺼내고(3-tuple 이상에는 제공되지 않음), curry는 언커리된 함수를 커리된 형태로, uncurry는 그 반대로 변환해요.
기타 함수
-- Misc functions
-- until p f yields the result of applying f until p holds.
until :: (a -> Bool) -> (a -> a) -> a -> a
until p f x
| p x = x
| otherwise = until p f (f x)
-- asTypeOf is a type-restricted version of const. It is usually used
-- as an infix operator, and its typing forces its first argument
-- (which is usually overloaded) to have the same type as the second.
asTypeOf :: a -> a -> a
asTypeOf = const
-- error stops execution and displays an error message
error :: String -> a
error = primError
-- It is expected that compilers will recognize this and insert error
-- messages that are more appropriate to the context in which undefined
-- appears.
undefined :: a
undefined = error "Prelude.undefined"
until은 조건 p가 참이 될 때까지 함수 f를 반복 적용해요. asTypeOf는 const의 타입 제한 버전으로, 첫 인자의 타입을 두 번째 인자와 강제로 일치시켜요. error는 실행을 멈추고 오류 메시지를 출력하는 원시 함수이고, undefined는 error "Prelude.undefined"로 정의된, 아직 정의되지 않은 값을 나타내는 값이에요.
9.1 표준 리스트 함수 (PreludeList)
이 절은 표준 리스트 함수들을 정의하는 PreludeList 모듈의 전체 구현이에요.
-- Standard list functions
module PreludeList (
map, (++), filter, concat, concatMap,
head, last, tail, init, null, length, (!!),
foldl, foldl1, scanl, scanl1, foldr, foldr1, scanr, scanr1,
iterate, repeat, replicate, cycle,
take, drop, splitAt, takeWhile, dropWhile, span, break,
lines, words, unlines, unwords, reverse, and, or,
any, all, elem, notElem, lookup,
sum, product, maximum, minimum,
zip, zip3, zipWith, zipWith3, unzip, unzip3)
where
import qualified Data.Char(isSpace)
infixl 9 !!
infixr 5 ++
infix 4 ‘elem‘, ‘notElem‘
매핑과 이어붙이기
-- Map and append
map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs
(++) :: [a] -> [a] -> [a]
[] ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)
filter :: (a -> Bool) -> [a] -> [a]
filter p [] = []
filter p (x:xs) | p x = x : filter p xs
| otherwise = filter p xs
concat :: [[a]] -> [a]
concat xss = foldr (++) [] xss
concatMap :: (a -> [b]) -> [a] -> [b]
concatMap f = concat . map f
map은 리스트의 각 원소에 함수를 적용하고, (++)는 두 리스트를 이어붙여요. filter는 조건을 만족하는 원소만 남기고, concat은 리스트의 리스트를 평탄화하며, concatMap은 매핑과 평탄화를 결합해요.
head·tail·last·init·null·length (리스트의 앞·뒤 원소와 길이)
-- head and tail extract the first element and remaining elements,
-- respectively, of a list, which must be non-empty. last and init
-- are the dual functions working from the end of a finite list,
-- rather than the beginning.
head :: [a] -> a
head (x:_) = x
head [] = error "Prelude.head: empty list"
tail :: [a] -> [a]
tail (_:xs) = xs
tail [] = error "Prelude.tail: empty list"
last :: [a] -> a
last [x] = x
last (_:xs) = last xs
last [] = error "Prelude.last: empty list"
init :: [a] -> [a]
init [x] = []
init (x:xs) = x : init xs
init [] = error "Prelude.init: empty list"
null :: [a] -> Bool
null [] = True
null (_:_) = False
-- length returns the length of a finite list as an Int.
length :: [a] -> Int
length [] = 0
length (_:l) = 1 + length l
head/tail은 (비어 있지 않은) 리스트의 첫 원소와 나머지를, last/init은 유한 리스트의 끝에서 같은 일을 해요. 빈 리스트에 적용하면 error로 예외를 던져요. null은 리스트가 비었는지 검사하고, length는 유한 리스트의 길이를 Int로 돌려줘요.
리스트 인덱스 연산자 (!!)
-- List index (subscript) operator, 0-origin
(!!) :: [a] -> Int -> a
xs !! n | n < 0 = error "Prelude.!!: negative index"
[] !! _ = error "Prelude.!!: index too large"
(x:_) !! 0 = x
(_:xs) !! n = xs !! (n-1)
(!!)는 0-기반의 리스트 인덱스 연산자예요. 음의 인덱스나 범위를 벗어난 인덱스는 error를 일으켜요.
fold·scan 계열
-- foldl, applied to a binary operator, a starting value (typically the
-- left-identity of the operator), and a list, reduces the list using
-- the binary operator, from left to right:
-- foldl f z [x1, x2, ..., xn] == (...((z ‘f‘ x1) ‘f‘ x2) ‘f‘...) ‘f‘ xn
-- foldl1 is a variant that has no starting value argument, and thus must
-- be applied to non-empty lists. scanl is similar to foldl, but returns
-- a list of successive reduced values from the left:
-- scanl f z [x1, x2, ...] == [z, z ‘f‘ x1, (z ‘f‘ x1) ‘f‘ x2, ...]
-- Note that last (scanl f z xs) == foldl f z xs.
-- scanl1 is similar, again without the starting element:
-- scanl1 f [x1, x2, ...] == [x1, x1 ‘f‘ x2, ...]
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs
foldl1 :: (a -> a -> a) -> [a] -> a
foldl1 f (x:xs) = foldl f x xs
foldl1 _ [] = error "Prelude.foldl1: empty list"
scanl :: (a -> b -> a) -> a -> [b] -> [a]
scanl f q xs = q : (case xs of
[] -> []
x:xs -> scanl f (f q x) xs)
scanl1 :: (a -> a -> a) -> [a] -> [a]
scanl1 f (x:xs) = scanl f x xs
scanl1 _ [] = []
-- foldr, foldr1, scanr, and scanr1 are the right-to-left duals of the
-- above functions.
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
foldr1 :: (a -> a -> a) -> [a] -> a
foldr1 f [x] = x
foldr1 f (x:xs) = f x (foldr1 f xs)
foldr1 _ [] = error "Prelude.foldr1: empty list"
scanr :: (a -> b -> b) -> b -> [a] -> [b]
scanr f q0 [] = [q0]
scanr f q0 (x:xs) = f x q : qs
where qs@(q:_) = scanr f q0 xs
scanr1 :: (a -> a -> a) -> [a] -> [a]
scanr1 f [] = []
scanr1 f [x] = [x]
scanr1 f (x:xs) = f x q : qs
where qs@(q:_) = scanr1 f xs
foldl은 이진 연산자와 시작 값, 리스트를 받아 왼쪽에서 오른쪽으로 축약하고, foldl1은 시작 값 없이 비어 있지 않은 리스트에 적용돼요. scanl은 축약의 각 단계 결과를 리스트로 모아 돌려주고, foldr/foldr1/scanr/scanr1은 오른쪽에서 왼쪽으로 동작하는 짝이에요.
무한 리스트 생성 함수
-- iterate f x returns an infinite list of repeated applications of f to x:
-- iterate f x == [x, f x, f (f x), ...]
iterate :: (a -> a) -> a -> [a]
iterate f x = x : iterate f (f x)
-- repeat x is an infinite list, with x the value of every element.
repeat :: a -> [a]
repeat x = xs where xs = x:xs
-- replicate n x is a list of length n with x the value of every element
replicate :: Int -> a -> [a]
replicate n x = take n (repeat x)
-- cycle ties a finite list into a circular one, or equivalently,
-- the infinite repetition of the original list. It is the identity
-- on infinite lists.
cycle :: [a] -> [a]
cycle [] = error "Prelude.cycle: empty list"
cycle xs = xs' where xs' = xs ++ xs'
iterate f x는 f를 반복 적용한 무한 리스트 [x, f x, f (f x), ...]를 만들어요. repeat x는 모든 원소가 x인 무한 리스트, replicate n x는 길이 n의 반복 리스트예요. cycle은 유한 리스트를 원형(무한 반복)으로 만들고, 빈 리스트에는 error를 던져요.
take·drop·splitAt·takeWhile·dropWhile·span·break (일부 취하기·버리기)
-- take n, applied to a list xs, returns the prefix of xs of length n,
-- or xs itself if n > length xs. drop n xs returns the suffix of xs
-- after the first n elements, or [] if n > length xs. splitAt n xs
-- is equivalent to (take n xs, drop n xs).
take :: Int -> [a] -> [a]
take n _ | n <= 0 = []
take _ [] = []
take n (x:xs) = x : take (n-1) xs
drop :: Int -> [a] -> [a]
drop n xs | n <= 0 = xs
drop _ [] = []
drop n (_:xs) = drop (n-1) xs
splitAt :: Int -> [a] -> ([a],[a])
splitAt n xs = (take n xs, drop n xs)
-- takeWhile, applied to a predicate p and a list xs, returns the longest
-- prefix (possibly empty) of xs of elements that satisfy p. dropWhile p xs
-- returns the remaining suffix. span p xs is equivalent to
-- (takeWhile p xs, dropWhile p xs), while break p uses the negation of p.
takeWhile :: (a -> Bool) -> [a] -> [a]
takeWhile p [] = []
takeWhile p (x:xs)
| p x = x : takeWhile p xs
| otherwise = []
dropWhile :: (a -> Bool) -> [a] -> [a]
dropWhile p [] = []
dropWhile p xs@(x:xs')
| p x = dropWhile p xs'
| otherwise = xs
span, break :: (a -> Bool) -> [a] -> ([a],[a])
span p [] = ([],[])
span p xs@(x:xs')
| p x = (x:ys,zs)
| otherwise = ([],xs)
where (ys,zs) = span p xs'
break p = span (not . p)
take n xs는 앞 n개를, drop n xs는 첫 n개를 버린 나머지를 돌려줘요. splitAt n xs는 (take n xs, drop n xs)와 같아요. takeWhile p는 p를 만족하는 가장 긴 접두사를, dropWhile p는 나머지 접미사를 돌려주고, span p는 그 둘을 쌍으로 묶으며, break p는 p의 부정을 쓰는 span이에요.
lines·words·unlines·unwords·reverse (문자열 분할·결합·뒤집기)
-- lines breaks a string up into a list of strings at newline characters.
-- The resulting strings do not contain newlines. Similary, words
-- breaks a string up into a list of words, which were delimited by
-- white space. unlines and unwords are the inverse operations.
-- unlines joins lines with terminating newlines, and unwords joins
-- words with separating spaces.
lines :: String -> [String]
lines "" = []
lines s = let (l, s') = break (== '\n') s
in l : case s' of
[] -> []
(_:s'') -> lines s''
words :: String -> [String]
words s = case dropWhile Char.isSpace s of
"" -> []
s' -> w : words s''
where (w, s'') = break Char.isSpace s'
unlines :: [String] -> String
unlines = concatMap (++ "\n")
unwords :: [String] -> String
unwords [] = ""
unwords ws = foldr1 (\w s -> w ++ ' ':s) ws
-- reverse xs returns the elements of xs in reverse order. xs must be finite.
reverse :: [a] -> [a]
reverse = foldl (flip (:)) []
lines는 문자열을 개행 문자 기준으로 나누고(결과에 개행 미포함), words는 공백으로 구분된 단어들로 나눠요. unlines/unwords는 그 역연산이에요. reverse는 유한 리스트의 원소 순서를 뒤집어요.
and·or·any·all·elem·notElem·lookup (부울·원소·검색)
-- and returns the conjunction of a Boolean list. For the result to be
-- True, the list must be finite; False, however, results from a False
-- value at a finite index of a finite or infinite list. or is the
-- disjunctive dual of and.
and, or :: [Bool] -> Bool
and = foldr (&&) True
or = foldr (||) False
-- Applied to a predicate and a list, any determines if any element
-- of the list satisfies the predicate. Similarly, for all.
any, all :: (a -> Bool) -> [a] -> Bool
any p = or . map p
all p = and . map p
-- elem is the list membership predicate, usually written in infix form,
-- e.g., x ‘elem‘ xs. notElem is the negation.
elem, notElem :: (Eq a) => a -> [a] -> Bool
elem x = any (== x)
notElem x = all (/= x)
-- lookup key assocs looks up a key in an association list.
lookup :: (Eq a) => a -> [(a,b)] -> Maybe b
lookup key [] = Nothing
lookup key ((x,y):xys)
| key == x = Just y
| otherwise = lookup key xys
and/or는 부울 리스트의 논리곱·논리합이고, any/all은 조건을 만족하는 원소가 하나라도 있는지/모두 만족하는지를 검사해요. elem/notElem은 리스트 원소 포함 여부이고, lookup은 연관 리스트에서 키에 대응하는 값을 Maybe로 찾아요.
sum·product·maximum·minimum (합·곱·최댓값·최솟값)
-- sum and product compute the sum or product of a finite list of numbers.
sum, product :: (Num a) => [a] -> a
sum = foldl (+) 0
product = foldl (⋆) 1
-- maximum and minimum return the maximum or minimum value from a list,
-- which must be non-empty, finite, and of an ordered type.
maximum, minimum :: (Ord a) => [a] -> a
maximum [] = error "Prelude.maximum: empty list"
maximum xs = foldl1 max xs
minimum [] = error "Prelude.minimum: empty list"
minimum xs = foldl1 min xs
sum은 유한 숫자 리스트의 합, product는 곱을 계산해요. maximum/minimum은 비어 있지 않은 유한·순서화된 리스트에서 최댓값/최솟값을 돌려주고, 빈 리스트는 error예요.
zip·zip3·zipWith·zipWith3·unzip·unzip3
-- zip takes two lists and returns a list of corresponding pairs. If one
-- input list is short, excess elements of the longer list are discarded.
-- zip3 takes three lists and returns a list of triples. Zips for larger
-- tuples are in the List library
zip :: [a] -> [b] -> [(a,b)]
zip = zipWith (,)
zip3 :: [a] -> [b] -> [c] -> [(a,b,c)]
zip3 = zipWith3 (,,)
-- The zipWith family generalises the zip family by zipping with the
-- function given as the first argument, instead of a tupling function.
-- For example, zipWith (+) is applied to two lists to produce the list
-- of corresponding sums.
zipWith :: (a->b->c) -> [a]->[b]->[c]
zipWith z (a:as) (b:bs)
= z a b : zipWith z as bs
zipWith _ _ _ = []
zipWith3 :: (a->b->c->d) -> [a]->[b]->[c]->[d]
zipWith3 z (a:as) (b:bs) (c:cs)
= z a b c : zipWith3 z as bs cs
zipWith3 _ _ _ _ = []
-- unzip transforms a list of pairs into a pair of lists.
unzip :: [(a,b)] -> ([a],[b])
unzip = foldr (\(a,b) ~(as,bs) -> (a:as,b:bs)) ([],[])
unzip3 :: [(a,b,c)] -> ([a],[b],[c])
unzip3 = foldr (\(a,b,c) ~(as,bs,cs) -> (a:as,b:bs,c:cs))
([],[],[])
zip은 두 리스트를 대응 쌍으로 묶고(짧은 쪽에 맞춰 짝지음), zip3은 세 리스트를 묶어요. zipWith 계열은 튜플 대신 첫 인자로 받은 함수로 짝지으며, unzip/unzip3은 쌍·삼중 리스트를 성분별 리스트로 분리해요.
9.2 표준 텍스트·읽기/쓰기 (PreludeText)
이 절은 텍스트 렌더링(Show)과 파싱(Read)을 위한 PreludeText 모듈의 전체 구현이에요.
module PreludeText (
ReadS, ShowS,
Read(readsPrec, readList),
Show(showsPrec, show, showList),
reads, shows, read, lex,
showChar, showString, readParen, showParen ) where
-- The instances of Read and Show for
-- Bool, Maybe, Either, Ordering
-- are done via "deriving" clauses in Prelude.hs
import Data.Char(isSpace, isAlpha, isDigit, isAlphaNum,
showLitChar, readLitChar, lexLitChar)
import Numeric(showSigned, showInt, readSigned, readDec, showFloat,
readFloat, lexDigits)
Read·Show 클래스와 타입 동의어
type ReadS a = String -> [(a,String)]
type ShowS = String -> String
class Read a where
readsPrec :: Int -> ReadS a
readList :: ReadS [a]
-- Minimal complete definition:
-- readsPrec
readList = readParen False (\r -> [pr | ("[",s) <- lex r,
pr <- readl s])
where readl s = [([],t) | ("]",t) <- lex s] ++
[(x:xs,u) | (x,t) <- reads s,
(xs,u) <- readl' t]
readl' s = [([],t) | ("]",t) <- lex s] ++
[(x:xs,v) | (",",t) <- lex s,
(x,u) <- reads t,
(xs,v) <- readl' u]
class Show a where
showsPrec :: Int -> a -> ShowS
show :: a -> String
showList :: [a] -> ShowS
-- Mimimal complete definition:
-- show or showsPrec
showsPrec _ x s = show x ++ s
show x = showsPrec 0 x ""
showList [] = showString "[]"
showList (x:xs) = showChar '[' . shows x . showl xs
where showl [] = showChar ']'
showl (x:xs) = showChar ',' . shows x .
showl xs
ReadS a는 파서 타입(문자열을 받아 결과와 남은 문자열의 목록을 돌려줌), ShowS는 문자열-생성 함수 타입이에요. 클래스 Read는 readsPrec(연산자 우선순위를 고려한 파싱)와 readList(리스트 파싱)를 정의하고, Show는 showsPrec·show·showList를 정의해요.
reads·shows·read·showChar·showString·showParen·readParen (읽기·쓰기·괄호 처리)
reads :: (Read a) => ReadS a
reads = readsPrec 0
shows :: (Show a) => a -> ShowS
shows = showsPrec 0
read :: (Read a) => String -> a
read s = case [x | (x,t) <- reads s, ("","") <- lex t] of
[x] -> x
[] -> error "Prelude.read: no parse"
_ -> error "Prelude.read: ambiguous parse"
showChar :: Char -> ShowS
showChar = (:)
showString :: String -> ShowS
showString = (++)
showParen :: Bool -> ShowS -> ShowS
showParen b p = if b then showChar '(' . p . showChar ')' else p
readParen :: Bool -> ReadS a -> ReadS a
readParen b g = if b then mandatory else optional
where optional r = g r ++ mandatory r
mandatory r = [(x,u) | ("(",s) <- lex r,
(x,t) <- optional s,
(")",u) <- lex t ]
reads는 우선순위 0으로 읽는 ReadS, shows는 우선순위 0으로 쓰는 ShowS예요. read는 전체를 소비하는 유일한 파싱이 있으면 그 값을, 없으면 "no parse", 여럿이면 "ambiguous parse" 오류를 내요. showChar/showString은 문자·문자열을 출력 문자열에 덧붙이고, showParen/readParen은 조건부 괄호 출력·파싱을 다뤄요.
lex 어휘 분석기
-- This lexer is not completely faithful to the Haskell lexical syntax.
-- Current limitations:
-- Qualified names are not handled properly
-- Octal and hexidecimal numerics are not recognized as a single token
-- Comments are not treated properly
lex :: ReadS String
lex "" = [("","")]
lex (c:s)
| isSpace c = lex (dropWhile isSpace s)
lex ('\'':s) = [('\'':ch++"'", t) | (ch,'\'':t) <- lexLitChar s,
ch /= "'" ]
lex ('"':s) = [('"':str, t) | (str,t) <- lexString s]
where
lexString ('"':s) = ["\"",s]
lexString s = [(ch++str, u)
| (ch,t) <- lexStrItem s,
(str,u) <- lexString t ]
lexStrItem ('\\':'&':s) = ["\\&",s]
lexStrItem ('\\':c:s) | isSpace c
= ["\\&",t) |
'\\':t <-
[dropWhile isSpace s]]
lexStrItem s = lexLitChar s
lex (c:s) | isSingle c = [([c],s)]
| isSym c = [(c:sym,t) | (sym,t) <- [span isSym s]]
| isAlpha c = [(c:nam,t) | (nam,t) <- [span isIdChar s]]
| isDigit c = [(c:ds++fe,t) | (ds,s) <- [span isDigit s],
(fe,t) <- lexFracExp s ]
| otherwise = [] -- bad character
where
isSingle c = c ‘elem‘ ",;()[]{}_‘"
isSym c = c ‘elem‘ "!@#$%&⋆+./<=>?\\^|:-~"
isIdChar c = isAlphaNum c || c ‘elem‘ "_'"
lexFracExp ('.':c:cs) | isDigit c
= [('.':ds++e,u) | (ds,t) <- lexDigits (c:cs),
(e,u) <- lexExp t]
lexFracExp s = lexExp s
lexExp (e:s) | e ‘elem‘ "eE"
= [(e:c:ds,u) | (c:t) <- [s], c ‘elem‘ "+-",
(ds,u) <- lexDigits t] ++
[(e:ds,t) | (ds,t) <- lexDigits s]
lexExp s = [("",s)]
lex는 문자열을 Haskell 어휘 토큰으로 분해하는 ReadS String이에요. 공백을 건너뛰고, 문자 리터럴·문자열 리터럴, 단일 기호, 기호열, 식별자, 숫자(소수·지수 포함)를 토큰으로 인식해요. 이 렉서는 완전히 충실하지 않아서 정규화된 이름, 8·16진수 토큰, 주석은 제대로 처리하지 못해요.
표준 타입들의 Read·Show 인스턴스
instance Show Int where
showsPrec n = showsPrec n . toInteger
-- Converting to Integer avoids
-- possible difficulty with minInt
instance Read Int where
readsPrec p r = [(fromInteger i, t) | (i,t) <- readsPrec p r]
-- Reading at the Integer type avoids
-- possible difficulty with minInt
instance Show Integer where
showsPrec = showSigned showInt
instance Read Integer where
readsPrec p = readSigned readDec
instance Show Float where
showsPrec p = showFloat
instance Read Float where
readsPrec p = readSigned readFloat
instance Show Double where
showsPrec p = showFloat
instance Read Double where
readsPrec p = readSigned readFloat
instance Show () where
showsPrec p () = showString "()"
instance Read () where
readsPrec p = readParen False
(\r -> [((),t) | ("(",s) <- lex r,
(")",t) <- lex s ] )
instance Show Char where
showsPrec p '\'' = showString "'\\''"
showsPrec p c = showChar '\'' . showLitChar c . showChar '\''
showList cs = showChar '"' . showl cs
where showl "" = showChar '"'
showl ('"':cs) = showString "\\\"" . showl cs
showl (c:cs) = showLitChar c . showl cs
instance Read Char where
readsPrec p = readParen False
(\r -> [(c,t) | ('\'':s,t)<- lex r,
(c,"\'") <- readLitChar s])
readList = readParen False (\r -> [(l,t) | ('"':s, t) <- lex r,
(l,_) <- readl s ])
where readl ('"':s) = [("",s)]
readl ('\\':'&':s) = readl s
readl s = [(c:cs,u) | (c ,t) <- readLitChar s,
(cs,u) <- readl t ]
instance (Show a) => Show [a] where
showsPrec p = showList
instance (Read a) => Read [a] where
readsPrec p = readList
Int, Integer, Float, Double, (), Char, 그리고 리스트 타입 각각에 Show/Read 인스턴스가 정의돼요. Int는 Integer를 경유해 minInt 문제를 피하고, 문자는 리터럴 표기('\'', "...")로 인코딩·디코딩되며, 리스트는 showList/readList를 사용해요.
튜플의 Read·Show 인스턴스
-- Tuples
instance (Show a, Show b) => Show (a,b) where
showsPrec p (x,y) = showChar '(' . shows x . showChar ',' .
shows y . showChar ')'
instance (Read a, Read b) => Read (a,b) where
readsPrec p = readParen False
(\r -> [((x,y), w) | ("(",s) <- lex r,
(x,t) <- reads s,
(",",u) <- lex t,
(y,v) <- reads u,
(")",w) <- lex v ] )
-- Other tuples have similar Read and Show instances
2-튜플의 Show/Read 인스턴스는 괄호와 쉼표로 감싼 형태로 렌더링·파싱하며, 다른 크기의 튜플도 유사한 인스턴스를 가져요.
9.3 기본 입출력 (PreludeIO)
이 절은 기본 입출력 액션을 정의하는 PreludeIO 모듈의 전체 구현이에요.
module PreludeIO (
FilePath, IOError, ioError, userError, catch,
putChar, putStr, putStrLn, print,
getChar, getLine, getContents, interact,
readFile, writeFile, appendFile, readIO, readLn
) where
import PreludeBuiltin
파일 경로, 오류 타입과 오류 함수
type FilePath = String
data IOError -- The internals of this type are system dependent
instance Show IOError where ...
instance Eq IOError where ...
ioError :: IOError -> IO a
ioError = primIOError
userError :: String -> IOError
userError = primUserError
catch :: IO a -> (IOError -> IO a) -> IO a
catch = primCatch
FilePath는 String의 동의어이고, IOError는 입출력 오류를 나타내는 시스템 의존적 추상 타입이에요. ioError는 IOError를 IO 예외로 던지고, userError는 메시지 문자열로 IOError를 만들며, catch는 IO 액션에서 IOError를 잡아 처리하는 함수예요.
출력 함수
putChar :: Char -> IO ()
putChar = primPutChar
putStr :: String -> IO ()
putStr s = mapM_ putChar s
putStrLn :: String -> IO ()
putStrLn s = do putStr s
putStr "\n"
print :: Show a => a -> IO ()
print x = putStrLn (show x)
putChar는 문자 하나를, putStr은 문자열을, putStrLn은 문자열 뒤에 개행을 붙여 출력해요. print는 값을 show로 문자열화해 개행과 함께 출력해요.
입력 함수
getChar :: IO Char
getChar = primGetChar
getLine :: IO String
getLine = do c <- getChar
if c == '\n' then return "" else
do s <- getLine
return (c:s)
getContents :: IO String
getContents = primGetContents
interact :: (String -> String) -> IO ()
-- The hSetBuffering ensures the expected interactive behaviour
interact f = do hSetBuffering stdin NoBuffering
hSetBuffering stdout NoBuffering
s <- getContents
putStr (f s)
getChar는 문자 하나를 읽고, getLine은 개행까지의 줄을 읽어요. getContents는 표준 입력 전체를 지연(lazily) 읽고, interact는 전체 입력을 함수로 변환해 출력하는 파이프라인을 제공해요.
파일 입출력
readFile :: FilePath -> IO String
readFile = primReadFile
writeFile :: FilePath -> String -> IO ()
writeFile = primWriteFile
appendFile :: FilePath -> String -> IO ()
appendFile = primAppendFile
readFile은 파일 내용을 문자열로 읽고, writeFile은 파일에 문자열을 쓰며, appendFile은 파일 끝에 문자열을 덧붙여요.
readIO·readLn
-- raises an exception instead of an error
readIO :: Read a => String -> IO a
readIO s = case [x | (x,t) <- reads s, ("","") <- lex t] of
[x] -> return x
[] -> ioError (userError "Prelude.readIO: no parse")
_ -> ioError (userError "Prelude.readIO: ambiguous parse")
readLn :: Read a => IO a
readLn = do l <- getLine
r <- readIO l
return r
readIO는 read와 같지만 파싱 실패 시 오류(error) 대신 예외(ioError (userError ...))를 일으켜요. readLn은 한 줄을 읽어 Read 타입 값으로 변환해 돌려줘요.
더 알아보기
- 6장 미리 정의된 타입과 클래스 (Predefined Types and Classes) — 이 장이 구현하는 클래스·타입의 개요와 법칙을 봐요.
- 5장 모듈 (Modules) — 5.6절의 Prelude 모듈 규칙을 봐요.
- 7장 기본 입출력 (Basic Input/Output) — Prelude가 제공하는 기본 I/O 액션을 봐요.
- 20장 Data.List —
PreludeList함수들의 일반화(generic) 버전을 봐요.