2편이다.


각 언어의 특징으로만 보라는 이상한 말


내가 좋아하는 책이라 여러 권을 산 책이 있다. Objective-C라는 책인데 난 2권 다 있다. 물론, 내가 책을 쓸 때도 이 책의 영향을 많이 받았다.(2.0버전) 마크 달림플(Mark Dalrymple), 스콧 내스 터(Scott Knaster), 와카르 말릭(Waqar Malik). 실무 개발자 3인방의 내공을 책으로도 크게 느낄 수가 있다. 사실, 실무를 오래 한 프로그래머가 깊게 파보면 다른 프로그래밍 언어 책과 큰 내용 차이는 없다. 작은 두 가지의 의견을 새롭게 낼 뿐이었다. 그러나 그 작은 의견은 아는 사람이 보면, 프로그래밍의 정수를 담은 것이라 하겠다. indirection layer와 이 세상의 문제를 완벽하게 해결해 줄 수 있는 단일 프로그래밍 언어는 존재하지 않는다는 것이다. 그래서 각 언어의 특징은 그 언어로 이해하라는 식의 글귀가 있었다.(책 뽑아서 보면 되지만 너무 아래 깔려 있다 ㅠ)


그중 후자는 시중의 책에서 가장 많이 인용되어 잘못 생각하는 사람이 많이 생겨나는 것 같아서 몇 글자 적어 본다. 다른 책이 원조일 수도 있겠지만, 그들의 프로그래밍 역사로 봤을 때 벤치마킹이 분명하다. 후자는 프로그래밍 언어는 바로 그 언어의 특징으로만 봐야 한다는 말이다. 어느 책이나 이 말을 돌려서 하던지, 직접적으로 적던지... 비슷하게 쓴다.


미국에서 사는 사람들의 문화, 한국 사람의 문화. 다르고 언어도 그 문화의 다름과 같이 1:1로 완벽하게 대치될 수 없는 부분이 있다. 그러나 뿌리가 판이하게 다르고 역사도 너무도 깊다. 그에 반해 컴퓨터의 역사를 깊지도 않고 그 뿌리는 결국 인텔 프로세서다. 혹은, ARM Instruction Set이다.


3인방이 말한 것도 한 가지 언어를 배울 때 집중해서 깊게 이해라 하는 뜻이지. 이기종의 언어에서 말하는 똑같은 개념까지도 다르게 표현하라는 뜻은 아니다. 한국말에 있는 부모님 마음속의 한, 인연 등 문화적 관점에서 이해해야 할 부분도 있거니와. 영어 사전에서 쉽게 찾을 수 있는 한국말은 같다고 봐도 무방하듯이 각 프로그래밍 언어의 비슷한 부분은 비슷하게 이해하면 된다.


상속, 다형성, 캡슐화, interface, delegate, abstract, protocol


요즘 코딩 교육을 일반 교사가 직접 배워서 하기 때문에 가끔 지인을 만나 비 전공자 교사의 고충을 듣는다. 사실 나도 코딩 교사가 따로 있을 줄 알았는데 말이다. 어차피 모든 지식은 학교로 다시 돌려보내야 하는 것이 맞으니 몇 가지 적어 본다. 교사는 워낙 똑똑한 사람들이라 이미 지식은 꽉 차 있다. 두리뭉실한 개념을 이 분야의 묵은지가 연결만 시켜주면 되겠다. 비공개로 출판한 책에서 밝힌 적이 있는데, 글로 쓰는건 제대로 전달되지 않지만 끄적여 본다.(장자 윤편으로 검색해서 관련 글을 보시길)


어려운 개념들은 쉽게 말해도 관계없고, 또 그게 많다.


상속은

복사/붙여 넣기.


내가 프로그래밍 수업에서 상속을 가르칠 때도 이렇게 간단히 말한다.


썰을 더 풀면,

다형성은 배열 돌려서 동시 다발적으로 실행하게 만들기 위함이라고 했는데, 사실 실무에서는 그게 다다. 

void pointer가 java에서 object type이다. 그래서 모든 객체를 가리킬 수 있고 그것을 다형성이라 부른다. 


간단한 이론이지만 이것으로 메시지 큐를 구현하고, 또 애플이나 안드로이드 앱 프레임워크를 만든다. 어떤 알림이 오면 각 앱을 list 에 등록하고 for문을 돌면서 브로드 캐스팅 메시지를 날리게 되는 것이다. 


Xcode에서 싱글뷰 어플 test 하나 만들고 Viewcontroller에 넣으면 된다.


우선 해당 글에 있던 델이게이터 예제.

//

//  ViewController.swift

//  test

//

//  Created by Junho HA on 2018. 10. 5..

//  Copyright © 2018년 hajunho.com. All rights reserved.

//


import UIKit


class ViewController: UIViewController {


    override func viewDidLoad() {

        super.viewDidLoad()

        // Do any additional setup after loading the view, typically from a nib.

        let a : implementMe = 지나가던사람()

        a.날사랑하니()

    }

}


protocol implementMe {

    func 날사랑하니() -> Void

}


class 남자 : implementMe {

    func 날사랑하니() {

        print("...")

    }

}


class 관심있던남자 {

    func 날사랑하니() {

        print("어")

    }

}


에서 implementMe 를 상속받는 친구는 implementMe type으로 생성가능하다. 추상화된 포인터로 보면 되겠다.


행인을 하나 추가하고 배열로 선언하면 다음과 같다.


//

//  ViewController.swift

//  test

//

//  Created by Junho HA on 2018. 10. 5..

//  Copyright © 2018년 hajunho.com. All rights reserved.

//


import UIKit


class ViewController: UIViewController {


    override func viewDidLoad() {

        super.viewDidLoad()

        // Do any additional setup after loading the view, typically from a nib.

        let a : [implementMe] = [ 지나가던사람() , 행인1() ]


        a[0].날사랑하니()

        a[1].날사랑하니()

    }

}


protocol implementMe {

    func 날사랑하니() -> Void

}


class 남자 : implementMe {

    func 날사랑하니() {

        print("...")

    }

}


class 관심있던남자 {

    func 날사랑하니() {

        print("어")

    }

}


class 지나가던사람 : 관심있던남자, implementMe {

}


class 행인1 : implementMe {

    func 날사랑하니() {

        print("누구슈")

    }

}


a[0], a[1] 은


   for x in a {

            x.날사랑하니()

        }


로 치환 가능하다.


결국, 인터페이스 하나 던져주고 아래에서 다 구현하라고 하고 어떻게 구현했던 상위 포인터(Object  던 Void Pointer던 상위 포인터든)로 하위 구현부의 메소드를 for 문 돌려서 실행시켜 주면,


모든 앱의 이름도 반환 받을 수 있고,

모든 앱을 실행시킬수도,

모두 지울 수도,

모든 앱의 메시지 수신부에 메세지를 보낼 수도 있다.


다형성은 간단한 개념이지만 매우 강력한 아키텍쳐 패턴의 주요 개념이다. 참고로 우리가 패턴이라고 하는 것들은 대부분 모듈 패턴이다.


캡슐화는

변수 마음대로 못 바꾸게 하기.

가령 속도라는 변수가 있어서 자동차를 제어한다면 그 속도가 0보다 크고 200보다 크게 세팅되어야 한다는 조건을 걸어 주는 게 좋다. 그래서 변수를 직접 제어하지 안혹, getter/setter를 쓴다.


인터페이스는 상속자(상속받은 무언가)가 구현해야 할 껍데기다.

앱스트랙트는 껍데기에 살을 좀 붙인 것이다.


껍데기는 다중 상속이 가능하다.

살이 붙은 것(내용이 있는 것)은 다중 상속이 불가능하다.


다중 상속(복사/붙여 넣기) 해봤자. 껍데기는 껍데기일 뿐이다. 구현해야 할 것만 많아진다.

제약 사항이 많다는 것은 누가 편하게 쓰라고 만들어 놓으면서 제약을 걸어 놓은 것이다.


가령 허허벌판 일 때는 규칙이 없지만, 놀이터를 만들어 줄 테니 아이들만 뛰어놀게 하라는 제약처럼.


프로토콜은 인터페이스와 같다. 다만, 다른 애들이 대신 구현해 줄 수 있다.


protocol implementMe {

    func 날사랑하니() -> Void

}


라는 프로토콜이 있다. 프로토콜은 껍데기다.

Protocol methods must not have bodies


그럼 이 프로토콜(인터페이스, abstract class)를 상속하는 클래스는 무조건 구현을 해 줘야 한다.

class 남자 : implementMe {

    func 날사랑하니() {

        print("...")

    }

}

안하면,

Type '남자' does not conform to protocol 'implementMe'

맞지 않는다고 한다.


만약 구현하고 싶지 않은 누군가가 있다고 하자

class 지나가던사람 : implementMe {

}

이 경우엔 다른 클래스가 구현해도 된다.


class 관심있던남자 {

    func 날사랑하니() {

        print("어")

    }

}


class 지나가던사람 : 관심있던남자, implementMe {

}


관심있던 남자가 날사랑하니()를 구현해 놓았었으니 복/붙하면 implementMe의 요구사항을 충족시켜준다는 뜻. UITableViewDelegate, UIScrollViewDelegate 같이 복잡한 녀석들로 이런 개념으로 쉽게 분석이 가능하다. 다른 복/붙(상속)없이 해당 델리게이트만 상속하면 구현해야할 수많은 stub이 있다.



위에서 말한 모든 개념은 사실 아래의 요구사항으로 생겼다.


1. 잘 만들어 놓으니(framework, library, api,...)

2. 네가 맘대로 복/붙해서 가져다 써(상속)

3. 그런데 몇 가지는 네가 알아서 넣어야 하니까 구현을 해야 할 거야 내가 정해놓은 규칙대로 (인터페이스, 프로토콜, 앱스 트렉트 클래스/매소드)

4. 모두 구현하기 싫으면 다른 애들이 구현한 거 그대로 상속(복사)해서 써(델리게이트)

5. 내가 만든 것의 값은 마음대로 못 바꿔(접근 제한자, 캡슐화) 수치가 0이 되면 사람이 죽을 수도 있으니 0이 들어오면 에러를 발생시켜버릴 때(setter, assert)


그래서 사실 따지고 보면 다 같은 개념은 아니다.


그러나 실무에서는 비슷하다.


모듈을 보는 단계에서는 완전히 다르고,

아키텍처를 보는 단계 해서는 부분 부분 다르고,

시스템을 보는 관점에서는 완전히 같다.


프로그래밍은 어차피 메모리와 CPU의 장난.


lambda, 함수형 프로그래밍, 클로저

 

셋 다 같은 말이다.


모든 함수는 y=f(x) 에서 나왔다.


세상 간단.


함수형 프로그래밍은 람다 대수에서 나왔다.


람다 대수의 핵심은 무언가를 추상화하는 방법과 구체화 하는 방법에 대한 철학이고.

특징은 익명 함수라는 것.


클로저는 기본적으로 함수 포인터다.


    func 주문받기() -> [String] {

        let 주문목록 : [String] = ["짜장면", "짬뽕"]

        return 주문목록

    }


swift 에 이런 함수를 만들었다면 C의 함수포인터로


String (*v)();

v = 주문받기


이렇게 만들 수 있을 것이다.


그러나 swift 는 문법이 쉽다.


let v = 주문받기

var v = 주문받기


쉽게 만들 수 있다. 배가 고프니 중국집을 하나 만들어 본다. 배가 고파 자장면이 아닌 입에 촥 감기는 짜장면을 불러 본다.


class 중국집 {

    func 주문받기() -> [String] {

        let 주문목록 : [String] = ["짜장면", "짬뽕"]

        return 주문목록

    }

}


프로그래밍은 많은 사람들이 작업을 하기 때문에 수많은 클래스에 수많은 함수가 있다. 클래스는 메모리에 없으니 생성해서 만들어 줘야 함수를 쓸 수 있다.


let a : 중국집 =  중국집()

let b = a.주문받기()


자바였다면,

중국집  a = new 중국집();

이었겠지.


클래스 생성없이 메모리에 바로 생성시키는 방법도 있다. primitive type, new, malloc, static

static 은 swift에도 있으니


  static func 주문받기() -> [String] {

        let 주문목록 : [String] = ["짜장면", "짬뽕"]

        return 주문목록

    }

로 선언하면


print(중국집.주문받기())

가 가능하다.


내가 주문하려면, 파라미터를 넣고

  func 주문받기(s : [String]) -> [String] {

        let 주문목록 : [String] = s

        return 주문목록

    }


 let c : [String] = ["짜장면", "짬뽕"]

 print(a.주문받기(s: c))


혹은


  print(a.주문받기(s: ["짜장면2", "짬뽕2"]))

로 하면 된다.


위의 위키피디아 링크 "람다대수"를 보면 함수가 반드시 이름을 가질 필요는 없다. 는 구절이 있다.

b가 그렇다. print(b) 도 되지만, a.주문받기() 혹은 print(a.주문받기(s: ["짜장면2", "짬뽕2"])) 처럼 b를 안 써도 된다. b가 생긴다는 것은 상태 변수 혹은 속성 혹은 멤버변수 혹은 프로퍼티(다 같은 말이다)가 생긴다는 말인데 b 를 안쓰니 함수형 프로그래밍의 특징이 하나는 상태값을 저장하지 않는 것으로 귀결된다. C/C++, Objective-C, C#, JAVA, SWIFT 모두 함수형 프로그래밍 언어의 특징을 구현할 수 있다. 그러나 하나의 프로그래밍 패러다임만 구현하지 않는다.


이 상황에서 주문하는 쪽은 상태를 가질 필요가 없다(중국집 전화번호만 있으면 된다) 그러나 중국집은 주문 목록은 있어야 한다.



한타임 쉬고. <밥먹음 ㅋㅋ> 배부르니 중국집이 하기 싫어진다.


자, 여기까지 하고 문법 짚고 가보자.

보면, 클로저는

(parameters) -> return type in

statements

}

이렇게 선언한다고 한다.


swift 에서 함수 선언은


func #name(parameters) -> return type {

        function body

}


이랬다. func와 이름이 사라지고 {} 가 바깥으로 나와버려서 function body 시작점을 몰라 in 이 들어갔다.

별거 없다.


이제 중국집을 다시 만들고, 고객의 소리를 받아보자.


class 중국집 {

    private var 주문목록 : [String] = []

    static func 고객소리(x : ()->Void) {

        x() //고객의 소리는 x, 실행 명령어는 ()

    }

}


고객의 소리는 이렇게 만드는데,

    func name() -> Void {

            print("짜장면 더 맛있게 해주삼")

        }

        중국집.고객소리(x: name)


앞 서 말했 듯이 내가 func를 가질 필요는 없겠다.


중국집.고객소리(x: {})

중국집.고객소리(x: { () -> Void })

중국집.고객소리(x: { () -> Void in print("짜장면 더 맛있게 해주삼")})


그런데 () -> Void 이거 왠지 결국엔 C의 함수포인터랑 같다. 안돼! 난 최신 언어라구.


중국집.고객소리(x: { print("짜장면 더 맛있게 해주삼")})


이렇게도 가능하겠다.


        중국집.고객소리(x: { print("짜장면 더 맛있게 해주삼")

            print("짬뽕은 더 맵게")

        })


결국 실행은 사장이 하는 거고 구현을 클라이언트(함수를 호출하는 사람)이 하게 되었다. 그리하여 파라미터도 사장이 넣을 수 있다. 사장의 성깔이 좋은지 아닌지 중국집 사장이 직접 넣어보자.


class 중국집 {

    private var 주문목록 : [String] = []

    static func 고객소리(x : (Bool)->Void) {

        x(true)

    }

}


성격이 좋다고 넣었다. 그러면 고객은 한마디 더 할 수 있다.


        중국집.고객소리(x: { (사장성깔좋아) in

            print("짜장면 더 맛있게 해주삼")

            print("짬뽕은 더 맵게")

            if(사장성깔좋아) { print("진짜 맛없어요")}

        })


이렇게 클로저가 탄생하게 되었다. UI 기초 2편을 써야 하는데 스냅킷 내용이 반 이상이라. 클로저를 먼저 짚고 넘어가야할 것 같아서. 물론, 축약 안하고 써도 된다.


        중국집.고객소리(x: { (사장성깔좋아) -> Void in

            print("짜장면 더 맛있게 해주삼")

            print("짬뽕은 더 맵게")

            if(사장성깔좋아) { print("진짜 맛없어요")}

        })


그리고 사실 프로그래밍 할 때 이런 프로그래밍 언어적 특성이나 모듈 패턴을 몰라도 아키텍처만 잘 짜면 굳이 축약 코드로 도배 안해도 충분히 재 사용성이나 메모리 사용량은 감소한다.


결국 임베디드, C/C++을 해야 언어를 제대로 이해할 수 있다. 기초 서적은 외서를 보지만 가끔 국내 서적도 여러권 가서 내가 이해한 것이랑 비교해 보는데 생략된 부분이 너무도 많다. 물론, 나 역시도 써봐서 싣고자 했는데 까먹고 못 실었던 내용이 많으니... 비슷한 경우일 수도 있겠다.


그러나 람다 대수의 위키 페이지를 참고하는 게 함수형 프로그래밍을 더 확실히 이해할 수 있는 지름길이라 하겠다. fxJAVA 도 함수형 프로그래밍인데,...


사실 수학을 떠나, 프로그래밍 언어에서 완전히 함수형으로 된다는 것은 메모리 관리가 완전히 순차적으로 되어야 한다는 말이다. 메모리 청크 찾는 거야 운영체제 단에서 어쩔 수 없다고 해도 그 상위에서는 정말 순차적으로 들어가야 한다. 그래야 데이터 무결성도 지켜지고 액세스 속도도 빠르다.


뭐, 응용 프로그래머야 함수형 프로그래밍 흉내만 잘 내어도 쓸데없는 상태 변수를 만들지는 않겠지만, 결국 언어를 만든 사람이 적절한 타이밍에 메모리 관리를 효율적으로 하는 게 중요하다고 생각한다.


+ Recent posts