두 번째 모임
하스켈 모임의 두 번째 오프라인 모임은 2015년 3월 18일 (수요일) 18시 30분에 한양대학교 서울캠퍼스 IT/BT관 501호에서 열렸습니다. 모임 목표는 “하스켈을 써본 경험이 거의 혹은 전혀 없이도 하스켈로 실제 코드를 짜서 현실적인 문제 해결을 해보는 것”이었습니다.
이 모임에서는 키노루가 하스켈에서 파일 처리를 시도하여 파일을 읽고, 이해하고, 파일을 쓰는 프로그램을 만드는 과정을 소개하고 이것을 모두 함께 실습함으로써 하스켈이 어떤 실용적인 용도로 사용될 수 있는지 체험해 보았습니다.
{-# LANGUAGE OverloadedStrings #-}
import Control.Monad (forever)
import qualified Data.ByteString as B
import System.IO
import System.IO.Error
main :: IO ()
main = do
B.putStrLn "Processing ..."
withFile "input.txt" ReadMode $ \ ihdl ->
withFile "output.txt" WriteMode $ \ ohdl ->
untilIOError (selectiveTransfer ihdl ohdl)
B.putStrLn "Done!"
selectiveTransfer :: Handle -> Handle -> IO ()
selectiveTransfer input output = do
line <- B.hGetLine input
if "system:" `B.isPrefixOf` line
then B.hPutStrLn output line
else return ()
untilIOError :: IO a -> IO ()
untilIOError action = catchIOError (forever action) (\_ -> return ())
이 코드의 핵심 알고리즘은 selectiveTransfer
함수에 들어 있습니다. 이 함수는 파일 핸들 두 개를 인자로 받아서, 입력 핸들에서 한 줄을 읽어들인 뒤 (B.hGetLine input
) 그 줄이 특정한 바이트열로 시작하면 ("system:"
) 출력 핸들로 내보내는 (B.hPutStrLn output line
) 역할을 합니다.
굳이 이 코드를 최초의 하스켈 코드로 사용한 의도는 이렇습니다. 첫째로 파일을 열고 특정한 문자열로 시작하는 줄만 찾아서 복사한다는 것은 일상 생활에서도 흔히 필요할 수 있는 일이기 때문에 하스켈의 실용적인 쓸모를 느낄 수 있는 최소한의 예제를 구성하고자 했습니다.
둘째는 하스켈이 다른 프로그래밍 언어들과 사뭇 다르지도 않고, 또 배우기 위해서는 완전히 새로운 전산학 과목을 배우는 것처럼 노력을 들여야 하는 것도 아니라는 점입니다. 예를 들어 위의 코드와 같은 역할을 하는 파이선 코드를 짜 보면, 코드의 어느 부분이 어떤 역할을 하는지에 대해 일대일 대응이 가능할 정도입니다.
def main():
with open('input.txt', 'r') as fin:
with open('output.txt', 'w') as fout:
while True:
try:
selectiveTransfer(fin, fout)
except EOFError:
break
def selectiveTransfer(fin, fout):
line = fin.readline()
if line == '':
raise EOFError
if line.startswith('Sheldon:'):
fout.write(line)
if __name__ == '__main__':
main()
일반적으로 하스켈에 대해서는 순함수형 언어라는 이유로 순수성(purity), 참조 투명성(referential transparency)이 강조되기 때문에 하스켈을 초심자에게 설명할 때에는 입출력 등 부작용이 있는 작업을 되도록 회피하려는 경향이 있습니다. 하지만 실제로 하스켈로 입출력을 짜 보면 다른 언어에 비해 어려운 것도 아니고 복잡한 것도 아니고 모나드나 타입클래스 이론을 알아야 되는 것도 아니라는 점, 즉 “하스켈, 어렵지 않다”를 강조하고자 했습니다.