Type에 대해서...

하스켈 입문서의 샘플코드에서 $로 되어 있는 것을 .로 바꾸는 과정에서 에러가 나고 있습니다.
아래 프로그램의 어떤 부분을 고쳐야 할까요?

main = do cs <- getContents
                 putStr $ lastNLines 10 cs

lastNLines :: Int -> String -> String
lastNLines n = unlines.(takeLast n).lines

takeLast :: Int -> String -> String
takeLast n = reverse.(take n).reverse

tail.hs:5:38:
    Couldn't match expected type `String'
           against inferred type `String -> [String]'
    Probable cause: `lines' is applied to too few arguments
    In the second argument of `($)', namely `lines'
    In the second argument of `($)', namely `(takeLast n) $ lines'

라는 에러 메세지가 나옵니다.

고수님의 가르침 부탁드립니다.
아울러 좀더 하스켈 스런 프로그램이라면 어떻게 해야 할까요?

미리 감사드립니다.

댓글 보기 옵션

원하시는 댓글 전시 방법을 선택한 다음 "설정 저장"을 누르셔서 적용하십시오.

1. 들여쓰기가 보이지

1. 들여쓰기가 보이지 않아서 본문을 수정했습니다. [code]와 [/code]로 코드를 감싸면 고친 것처럼 예쁘게 출력이 됩니다.

2. lines는 String -> [String]입니다. 그러니까 takeLast도 Int -> String -> String이 아니라 Int -> [String] -> [String]이 되어야 합니다.

그런데 하스켈의 장점을 살리려면 굳이 takeLast를 Int -> [String] -> [String]로 하는 것보다 Int -> [a] -> [a]로 하는 것이 낫습니다.

한 걸음 더 나아가 하스켈의 자료형 추론 기능을 활용하면 알아서 Int -> [a] -> [a]로 추정해주므로 자료형을 일일이 지정해줄 필요없이 그냥 코드만 적으면 에러가 나지 않습니다.

3. 사실 간단한 코드가 아니면 가급적 .을 쓰지 않는 게 좋습니다. takeLast n = reverse.(take n).reverse라고 하는 것보다 takeLast n list = reverse $ take n $ reverse list라고 하는 편이 훨씬 읽기가 좋지요.

그런데 takeLast의 지금 구현 방식은 리스트를 두 번이나 뒤집는 방식이라 별로 좋지가 않습니다. 짧은 코드면 모르겠지만 길면 성능이 문제가 되겠죠. 차라리 drop을 이용해서 구현하는 편이 나을 것 같습니다.

감사합니다!

감사합니다.
말씀대로 형을 지정해 주지 않았더니 에러없이 컴파일이 되었습니다.
제가 지정하는 형에 문제가 있는 것 같습니다.

공부를 위해서 형을 이리저리 지정해 주는데 역시 문제가 생깁니다.

main = do cs <- getContents
             putStr $ lastNLines 10 cs

lastNLines :: Int -> [a] -> [a]
lastNLines n cs = unlines $ takeLast n $ lines cs

takeLast :: Int -> [a] -> [a]
takeLast n ss = reverse $ take n $ reverse ss

에러메세지는
tail.hs:7:0: Invalid type signature
라고 나옵니다.

추가로 제가 drop함수를 몰라서 코드를 말씀주신대로 고쳐보지는 못하였습니다.
답변에 다시 감사드립니다.

drop을 이용해 보았습니다.

main = do cs <- getContents
            putStr $ lastNLines 10 cs

lastNLines n cs = unlines $ takeLast n $ lines cs

takeLast n ss = drop (length ss - n) ss

좀더 하스켈스런 코드라면 어떻게 하실지 가르침 부탁드립니다.

귤님의 조언은

귤님의 조언은 "takeLast의 타입이 Int→String→String이 아닌 Int→[String]→[String]이어야하며, 보다 범용적으로는 Int→[a]→[a]로 일반화할 수 있다"는 뜻이었습니다. 함께 수정하신 lastNLines 함수의 타입은 Int→String→String이어야만 하며 이보다 일반화될 수 없습니다. (lines 함수의 정의역이 String이므로 (lastNLines n)의 정의역이 String으로 확정되고, unlines 함수의 공변역이 String이므로 (lastNLines n)의 공변역도 String으로 확정됩니다.)

main = do cs <- getContents
          putStr $ lastNLines 10 cs

lastNLines :: Int -> String -> String
lastNLines n cs = unlines $ takeLast n $ lines cs

takeLast :: Int -> [a] -> [a]
takeLast n ss = reverse $ take n $ reverse ss

감사합니다.

친절하신 답변에 진심으로 감사드립니다. 책을 읽어나가면서 잘 이해가 되지 않던 부분이 선생님의 조언으로 해소가 되는 느낌입니다. 다시 한번 감사드립니다.