IO Type

Programming/Haskell 2015/05/20 10:18


IO

Introduction to IO

IO a 

사이드 이펙트를 수행하고, 그 결과로 type a 의 value 를 리턴하는 abstract 타입을 고려할 수 있다.

statements 는 보통 사이드 이펙트를 통해 대부분 communicate 된다. I/O 모나드를 소개함으로써 하스켈에서 statement/action 의 개념을 소개한다. 그러나 이 개념 또한 expression 의 일환이라, 결합이 가능하다. (composition)

IO Char 

어떤 사이드이펙트를 수행하고 결과로 some character 를 리턴

IO ()

이 imperative type 에서도 가장 imperative 한 것이 IO () side effect 만 수행함

getChar :: IO Char
getChar :: () -> Char -- () -> something means trying to simulate laziness
putChar :: Char -> IO ()
return :: a -> IO a  -- 어떤것도 하지 않고 즉시 리턴 

a :: IO (Char, Char)
a = do x <- getChar
        getChar
        y <- getChar
        return (x,y)

standard input 으로 부터 3 문자를 읽어 첫번째, 세번째를 페어로 리턴

오른쪽에는 t 의 IO 가 위치하고 왼쪽에는 t 가 위치하여 두 타입이 다르므로, assign 연산자 대신 <- 를 사용하는 것.

getLine :: IO String
getLine = do x <- getChar
            if x == '\n' then
                return []
            else
                do xs <- getLine
                   return (x:xs)

이번엔 standard output 에 string 을 print 하는 IO 메서드

putStr :: String -> IO ()
putStr [] = return ()
putStr (x:xs) = do putChar x
                   pubStr xs

putStrLn :: String -> IO ()
putStrLn xs = do putStr xs
                 putChar '\n'

imperative code 나 I/O 코드를 다른 함수들과 사용할 수 있다는 데 그 강점이 있다. 특히 String 은 그 자체로 리스트이기때문에, 리스트에 사용되는 함수는 위와같은 방식으로의 활용도가 높다.

strlen :: IO ()
strlen = do putStr "Enter a string: "
            xs <- getLine
            putStr "The string has "
            putStr (show (length xs))
            putStrLn " characters"

pure code 를 작성하여 impure code 에 embeded 한 예라 할 수 있음.

저작자 표시 비영리 변경 금지

'Programming > Haskell' 카테고리의 다른 글

IO Type  (0) 2015/05/20
Parser in Haskell  (0) 2015/05/18
Parser 의 bind  (0) 2015/05/15

Parser in Haskell

Programming/Haskell 2015/05/18 21:04


1.What is a Parser ?

a + b * c 가 있을때 a , b , c 를 leaf 로 하고 연산자 (+, *) 를 parent 로 하는 트리 구조와 같이 나타낼 수 있는, 흔히 알고 있는 문자열을 구분할 수 있는 parser.

The Parser Type

type Parser = String -> Tree
type Parser = String -> (Tree, String) --not complete
type Parser = String -> [(Tree, String)] --list of result 
    can be empty - 하스켈에서는 리스트를 주로 이용. 다른 언어에서 많이 사용하는 Maybe 와 같이 비어 있는 타입과 값이 있는 경우를 구분하는 타입을 사용해도. 리스트 연산자들을 사용하기 편하므로 리스트를 쓰는게 관용 (idiom)
type Parser a = String -> [(a, String)] --We don't care Tree or not

문제를 간단하게 하기 위해, 성공한 경우에는 singleton list 를 ex. ['a', "hello"], 실패한 경우에는 빈 리스트를 리턴하는 경우만을 고려.

Basic Parsers

더 복잡한 Parser 구현을 위해 먼저 단순한 부분부터. 단일 문자 추출

item :: Parser Char
item = \inp -> case inp of
                []     ->  []
                (x:xs) ->  [(x, xs)]
-- 문자열 inp 를 받아 비어있는 경우는 빈리스트를, 데이터가 있는 경우는 첫번째 문자열과 나머지 문자열을 pair 로
-- 좌변에 패턴매칭을 사용하지 않아 두개 절을 사용하는 대신, 하나의 람다 표현식에 case 표현식을 사용하여 내부에서 패턴매칭을 사용함 -> 저자는 싱글 톤을 리턴하거나 빈 리스트를 리턴하는 함수임이 잘 나타나 있어 이런 형태를 좋아한다함

항상 실패하는 parser

failure :: Parser a
failure = \inp -> []

항상 성공하는 parser

return :: a -> Parser a
return v = \inp -> [(v, inp)]

parser p 가 성공한다면 parser p 와 같이 동작하고, 그렇지 않다면 parser q 와 같이 동작하는 parser (두 parser 를 받아 모두 시도해보는 parser)

-- 같은 타입을 parsing 하는 parser
(+++) :: Parser a -> Parser a -> Parser a     
p +++ q = \inp -> case p inp of
                    [] -> parse q inp
                    [(v, out)] -> [(v, out)]

parse :: Parser a -> String -> [(a, String)]
parse p inp = p inp

monads 그냥 특정 operation 이 있는 타입이라고 생각하라 함


2. Sequencing

parser 가 monad 이게 하는 다른 연산자 - (parser 의 sequence라 한다) - 를 살펴본다.

하나가 실패하면 다음을 시도하는것이 아니라 순차적으로 결합하고자 함.

이를 위해 do - syntax 라고 불리는 문법을 활용한다. (do 문법을 사용할 수 있는것은 모나드를 사용하는 장점 중에 하나이다.)

p :: Parser (Char, Char)
p = do x <- item
       item
       y <- item
       return (x, y)

parser 의 sequence 중 어느 하나라도 실패하면, 모두 실패한다.

Derived Primitives

predicate 를 만족하는 문자를 parsing 함

sat :: (Char -> Bool) -> Parser Char
-- 어떤 타입을 다른 타입을 통해 그 타입이 내포한 타입 시그니쳐를 표현 할 수 있다. 
-- sat :: (Char -> Bool) -> String -> [(a, String)]
sat p = do x <- item
           if p x then return x
           else failure

digit parsers

digit :: Parser Char
digit = sat isDigit

specific character parser

char :: Char -> Parser Char
char x = sat (x ==)
-- haskell 에서 sectioning (아마도 partial applying 을 지칭하는 것인듯) 을 하는 좋은 예
-- x 가 두군데 있으니 pointfree 스타일로 구성해 볼 수도 있다. 

parser 를 0 번 이상 적용하는 함수

many :: Parser a -> Parser [a]
-- String -> [(a, String)] -> String -> [([a], String)]
-- a 타입 파서를 받아서 a 의 리스트의 파서를 뱉는다 ?
many p = many1 p +++ return []
-- 먼저 최소한 한번의 Parser, p 를 적용해 보고 만약 실패하면 빈리스트를 리턴한다. 

many1 :: Parser a -> Parser [a]
many1 p = do v  <- p
             vs <- many p
             return (v:vs)
-- p 가 성공하면 다시 many 를 호출, 다시 parsing 을 시도한다. 
-- 성공한 파싱 결과들은 모두 v:vs 로 인해 append 된다.
-- 실패하는 순간 빈리스트가 append 되는것으로 종료. 
-- 왜, [a] 가 아니라 Parser [a] 인가 ? 
-- v <- p 이기 때문에..(결국 이녀석은 [a] 를 parsing 하는 함수를 리턴한다.)

parsing a specific string of characters

string :: String -> Parser String
string [] = return []
string (x:xs) = do char x
                   string xs
                   return (x:xs)
-- do sequence 에서 실패하면 이 함수 전체가 실패하므로, char 문자열에 반복적으로 적용하는 것으로 문자열을 파싱할 수 있다. 
-- 착각할 수 있는 부분이, 여기서 x 와 xs 는 파싱하고자하는 specific 문자열이지, 파싱 대상이 되는 문자열 ( input ) 이 아니다. 
-- Prefix 만을 파싱한다.

Example

1. parser that consumes a list of one or more digits from a string

p :: Parser String
p = do char '['
        d <- digit
        ds <- many (do char ','
                       digit)
        char ']'
        return (d:ds)
-- '[' 와 첫 번째 숫자를 파싱하고난 후에는 ',' 와 숫자로 이루어진 한개 이상의 문자를 파싱해야한다. 
-- 여기서 주목해야할 것은, many 의 인자 Parser a 에 다시 do 로 sequence parser 를 넣었다는 것이다. (익숙치 않아 어려웠다.)

> parse p "[1,2,3,4]"
[("1234"), ""]
> parse p "[1,2,3,4"
[]

3. Arithmetic Expressions

괄호를 사용한 수학식을 생각해보자.

표현식 문법 정의

expr -> term '+' expr | term
term -> factor '*' term | factor
factor -> digit | '(' expr ')'
digit -> '0' | '1' | ... | '9'
  • expr : term + expr 혹은 term - 즉, 0개 이상의 + 로 엮은 식
  • term : factor * term 혹은 factor - 즉, 0 개 이상의 * 로 엮은 식
  • factor : 숫자 혹은 (expr) - 단일 숫자 혹은 괄호로 엮은 식
  • digit : 0 ~ 9 - 수

massage to simpler definition

expr -> term ('+' expr | ɛ)
term -> factor ('*' term | ɛ)

여기서 엡실론 (ɛ) 은 어떤 것도 parsing 못하는 empty parser

expr :: Parser Int
expr = do t <- term
          do char '+'
             e <- expr
             return (t + e)
          +++ return t

term :: Parser Int
term = do f <- factor
          do char '*'
             t <- term
             return (f * t)
          +++ return f

factor :: Parser Int
factor = do d <- digit
            return (digitToInt d)
         +++ do char '('
                e <- expr
                char ')'
                return e

eval :: String -> Int
eval xs = fst (head (parse expr xs))

> eval "2*3+4"
10
> eval "2*(3+4)"
14


저작자 표시 비영리 변경 금지

'Programming > Haskell' 카테고리의 다른 글

IO Type  (0) 2015/05/20
Parser in Haskell  (0) 2015/05/18
Parser 의 bind  (0) 2015/05/15

Parser 의 bind

Programming/Haskell 2015/05/15 18:28

결론부터 이야기하자면, 잘 모르겠다. 

잘 모르겠으나 최대한 이해해 보려고 하는 차원에서 조금씩 뜯어보아 정리했다. 


여기서 bind ( >>= ) 는 시그니쳐를 풀어보면 a 타입의 parser 를 첫번째 인자로, a 타입을 인자로 해서 b 타입의 parser 로 변환하는 함수를 두번째 인자로해서 b 타입의 parser 를 리턴한다. 


정의를 살펴보면, .parse p inp 의 파싱이 성공한 경우 그 결과에 f 를 적용하여 Parser b 타입으로 ( 즉, b 타입을 파싱할 수 있는 함수 ) 변경하고, 이를 out 에 적용한다. ( 즉, out 으로부터 b 타입의 파싱을 시도한다 )


저작자 표시 비영리 변경 금지

'Programming > Haskell' 카테고리의 다른 글

IO Type  (0) 2015/05/20
Parser in Haskell  (0) 2015/05/18
Parser 의 bind  (0) 2015/05/15
1 2 3 4 5 ... 40


티스토리 툴바