336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

아래 코드를 보자. 

let total = 0;
let item = [
    {name : '새우깡', price: 100},
    {name : '고래밥', price: 200},
    {name : '꼬북칩', price: 300},
    {name : '하이트맥주', price: 400},
];

const addItem = (name, price) => {
    item.push({
        name : name,
        price : price
    });
}


addItem('땅콩', 400);

이전 글과 같은 이유로, addItem 은 액션이다. 

 - 암묵적 입/출력이 존재

 - 실행 시점 및 횟수에 영향을 받음

 

위를 "계산" 으로 바꾸려면 

 - 암묵적 입/출력을 명시적 입/출력으로 변경 

 - 실행 시점 및 횟수에 영향이 없도록 변경 

 

이전 글 처럼, 먼저 암묵적 입/출력을 명시적 입/출력으로 바꿔보면

let total = 0;
let item = [
    {name : '새우깡', price: 100},
    {name : '고래밥', price: 200},
    {name : '꼬북칩', price: 300},
    {name : '하이트맥주', price: 400},
];

const addItemCalc = (items, name, price) => {
    items.push({
        name : name,
        price : price
    });
    
    return items;
}

item = addItemCalc(item, '땅콩', 400);

console.log(item);

 명시적 입/출력으로 변경하였다. 암묵적 입/출력 문제가 사라진 듯 하지만, 암묵적 출력 문제는 존재한다.  

문제는 items 파라미터는 '참조' 로 전달이 되기 때문에, 함수 내 변경사항이 원본에 적용이 된다.

// items 파라미터는 참조로 전달된다, 즉 items 에 뭔가를 추가한다면,
// 파라미터로 전달된 원본도 같이 변경된다. 
// return 값이 존재하지만, 암묵적 출력은 여전히 남아있다.
const addItemCalc = (items, name, price) => {
    items.push({
        name : name,
        price : price
    });
    
    return items;
}

함수의 실행이, 함수 외부의 변수와 '완벽하게' 차단된다면 부수효과(side effect) 억제효과도 있고, 그만큼 테스트와 유지보수 용이성도 증가할 것이다. 

해결방법 중 하나는 '복사본 리턴' 이다. 

 

const addItemCalc = (items, name, price) => {
    let result = items.slice(); //여기서 복사본을 만든다.
    result.push({
        name : name,
        price : price
    });
    
    return result;
}

복사본을 만든 후, 배열을 변경 하고, 그 후에 변경된 배열을 리턴한다. 

 

위와같이 구현하면, 큰 자료구조라 하더라도, 조작 시, 로직과 데이터를 완벽하게 분리 해낼 수 있다. 

 

결론

동작을 계산으로 바꾸는 첫번째 방법 - 변경시에는 복사본을 사용하고 변경된 데이터를 리턴하라.

 

추가

- 객체 복사 시 성능 이슈가 있을 것이라 생각되는 분이 많을 듯 하다. 자바스크립트에서는 복사 시 기본적으로 얕은복사를 사용하고, 깊은 복사는 loadsh 등의 라이브러리를 사용하여 처리한다. 성능상의 이슈는 그리 크지 않은것으로 보이며, 필요할 경우에만 깊은 복사를 사용하면, 발생할 수 있는 성능상의 이슈를 줄일 수 있을 것이다. 

  참고 : https://jess2.xyz/JavaScript/copy/

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

아래 코드를 보자, 동작(Action) 이 있는 코드다.

let total = 0;
let item = [
    {name : '새우깡', price: 100},
    {name : '고래밥', price: 200},
    {name : '꼬북칩', price: 300},
    {name : '하이트맥주', price: 400},
];

const getTotalAmount = () => {
    for(let i = 0; i < 4; i++) {
        total += item[i].price;
    }
    console.log(`total: ${total}`)
};


getTotalAmount();

getTotalAmount 는 total에 item 배열의 price 값을 모두 더하는 간단한 함수.

 

위 코드에서 getTotalAmount 의 특징은 

1. 암묵적인 입/출력이 있다. 

  - 암묵적 입력 : item

  - 암묵적 출력 : total

2. 실행 횟수와 시점에 영향을 받는다. 

  - getTotalAmount 를 두번 호출한다면 item 배열의 요소를 두번 순회, total 값은 2000 이 된다. 

 

때문에, getTotalAmount 는 '동작' 이고, getTotalAmount 의 유지보수 및 테스트를 위해서는 코드 전체를 확인한 후, 

item 과 total 의 값 변경에 신경쓰며 작업해야한다.

작은 프로젝트에서야 저렇게 하는게 빠르고, 신경쓸 부분도 적기 때문에 상관이 없지만, 

프로젝트가 증가하고, 같이 일할 사람들이 생길수록 저런 부분은 복잡성을 증가시키고, 여러 개발자들이 같은 작업을 계속하게된다.

(암묵적 입/출력 분석 및 상태 변경 처리 등...)

 

위 코드에서 암묵적 입/출력을 명시적 입/출력으로만 변경하여도, 유지보수 시 작업의 복잡도를 줄일 수 있다.

//getTotalAmountCalc 의 계산버전
const getTotalAmountCalc = (itemList) => {
    let result = 0;
    for(let i = 0; i < 4; i++) {
        result += itemList[i].price;
    }
    return result;    
}

getTotalAmountCalc 함수가 갖는 특징 

1. 명시적인 입/출력이 생김 

  - 입력: itemList

  - 출력: result 

2. 실행 횟수와 시점의 영향을 받지 않음 

  - 100 번을 실행해도 itemList 가 같으면 항상 같은 값이 출력됨 

 

위 1,2 번의 특징으로 인해 갖는 장점 

1. 코드 테스트 부하가 상대적으로 감소 

  - 입력값에만 영향을 받으므로, 개발자가 원하는 특정 상황에서 테스트 가능

2. 유지보수 시 공수 감소 

  - 문서에는 '입력' 과 '출력' 타입 및 동작만 기술하면, 이후 개발자는 입출력만 신경써서 개발하면 된다.

    (정 못믿겠으면, 개발 전에 테스트용 입력값 갖고 테스트만 하면됨.)

 

위 1,2 번의 특징으로 인해 갖는 단점 

1. 코드량 증가 

  - 함수 첫부분 선언코드, 마지막의 return 코드 

2. 메모리 추가사용 

  - 함수 안의 지역변수 선언, 지금은 작은 사이즈라 상관없지만, 크기가 자꾸 커지면???

 

단점이 없지는 않지만, 향후 정리할 '얕은 복사' 와 '방어적 복사' 등을 사용하고, 

요즘 GC 들은 많은 부분에서 최적화가 이루어지고 있기 때문에, 기가바이트 급의 대용량이 아니라면, 

저 단점들은 '투자' 의 개념으로 생각하는게 좋지 않을까 한다. 

 

결론

동작을 계산으로 바꾸는 첫번째 방법 - 입/출력을 명시적으로 선언하라

  => 비지역 변수 read 는 함수의 파라미터로, 비지역변수 write 는 함수의 리턴값으로 바꿀 것.

 

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

코드는 세가지 종류로 구분할 수 있다.

 

1. 동작(Action) 

  - 실행 시간과 횟수에 영향을 받음

  - 같은 코드라 해도, 나오는 결과가 다를 수 있다. 

  - ex) 메일을 보내는 코드, DB 에 기록하는 코드

  - 명시적 입/출력(함수 파라미터, 리턴값)과 암묵적 입/출력이(전역변수, 객체변수 등의 비지역변수) 섞여있음

 

2. 계산(Calculation)

  - 입/출력이 모두 명시적입 

  - 출력값은 입력값에 대해서만 영향을 받음

  - 입력값이 같으면 항상 같은 값을 출력함

 

3. 데이터 

  - 이벤트 실행의 결과, 

  - 우리가 흔히 아는 변수(리스트, 값 등...)

 

소프트웨어 개발에서 '액션' 이 갖는 특징 

1. 구현 / 설계가 상대적으로 용이함 

  - C/C++, JAVA 구현하듯이 절차적으로 구현하면 됨 

2. 유지보수 및 테스트의 어려움 

  - 소프트웨어 규모가 증가할수록 많은 암묵적 입력이 존재(복잡성이 증가함) 

  - 테스트 자동화 진행 시, 모든 암묵적 입력값을 설정해줘야 함.

 

소프트웨어 개발에서 '계산' 이 갖는 특징 

1. 구현의 상대적인 까다로움

  - 초반 적응의 어려움 (방어적 복사 등... )

2. 유지보수 및 테스트의 상대적인 용이함 

  - 입력값이 같으면 출력값 또한 같다. (-> 유닛 테스트 용이)

 

때문에, 소프트웨어 개발 시, '액션' 의 갯수는 최소화하고, 가능하면 '계산' 을 사용하는게

유지보수에 편할 수 있음.

 

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Akka 코딩 공작소 예제중에

 

ScalaTest 3.2.0 과는 다른 부분이 있어 기록해둠.

 

원래 코드는 아래와 같다.

import org.scalatest.{WordSpecLike, MustMatchers}
import akka.testkit.TestKit
import akka.actor._

//This test is ignored in the BookBuild, it's added to the defaultExcludedNames

class SilentActor01Test extends TestKit(ActorSystem("testsystem"))
  with WordSpecLike
  with MustMatchers
  with StopSystemAfterAll {
  // Commented to make the travis build pass, this is the original test in the book
  "A Silent Actor" must {
    "change state when it receives a message, single threaded" in {
      //Write the test, first fail
      fail("not implemented yet")
    }
    
    "change state when it receives a message, multi-threaded" in {
      //Write the test, first fail
      fail("not implemented yet")
    }
  }
}

 

ScalaTest 3.2.0 에서는 객체들 이름이 변경되었다, 자세한 내용은 아래 링크를 참고바란다.

 

www.scalatest.org/release_notes/3.2.0

 

ScalaTest

ScalaTest/Scalactic 3.2.0 Release Notes ScalaTest/Scalactic 3.2.0 (for Scala 2.10, 2.11, 2.12, and 2.13; on the JVM, JavaScript, native, and Dotty) includes the enhancements and bug fixes listed below. No source code using ScalaTest/Scalactic 3.1.2 should

www.scalatest.org

예제 코드의 변경사항만 적어보자면,

 

WordSpecLike -> AnyWordSpecLike 로 변경 (org.scalatest.wordspec.AnyWordSpecLike 패키지 사용)

MustMatchers -> Matchers 로 변경 (org.scalatest.matchers.must.Matchers 패키지 사용)

 

두가지다, 이부분을 적용해보면,

 

class SilentActor01Test extends TestKit(ActorSystem("testsystem"))
  with AnyWordSpecLike /*scalatest 3.2.0 에서 변경*/
  with Matchers /*scalatest 3.2.0 에서 변경*/
  with StopSystemAfterAll {

  "A Silent Actor" must {

    "change state when it receives a message, single threaded " in {
      //Write the test, first fail
      fail("not implemented yet")
    }

    "change state when it receives a message, multi-threaded" in {
      //Write the test, first fail
      fail("not implemented yet")
    }
  }
}

이렇게 된다. 빌드도 정상적으로 된다.

 

참고들 하시길.

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Blender 상에서 아래와 같은 구조로 되어있을 때,  



getObjectByName 함수를 사용하면 각 객체들 (Mesh, Object3D, Camera, Light 등등) 을 불러올 수 있다. 


var cube2 = object.scene.getObjectByName("Cube")
var groupCylinder2 = object.scene.getObjectByName("GroupCylinder")
var cylinder2 = object.scene.getObjectByName("Cylinder")
var camera2 = object.scene.getObjectByName("Camera")
var light2 = object.scene.getObjectByName("Light")


하지만 Animation 의 경우는 읽어오질 못하는듯.... 혹시 읽는 방법 아시는분 댓글로 공유 부탁드립니다.

'프로그래밍' 카테고리의 다른 글

LuaJIT 의 성능 테스트  (0) 2010.01.11
블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Slick 으로 DB CRUD 를 해보고 있는데


DateTime 을 읽을 때 아래와 같은 오류가 난다.


java.time.format.DateTimeParseException: Text '2019-07-24 00:00:00' could not be parsed at index 10


"scala slick date" 키워드로 구글링 검색 결과, Slick 3.3.2 Upgrade Guide 가 나온다.  일단 들어가본다


https://scala-slick.org/doc/3.3.2/upgrade.html


사용중인 DBMS 는 mySQL.


Upgrade Guide 의 slick.jdbc.MySQLProfile 부분에 보면 DateTime -> LocalDateTime 으로 Parsing 될 때 


어떤 타입의 Texts를 읽는지 나온다. 


java.time.LocalDateTime TEXT '2019-02-03T18:20:28.661'


즉 yyyy-mm-dd'T'hh:MM:ss 형태로 있어야 하는데, T 가 없어 10번째 인덱스를 로딩하는중에 에러가 났던 것.


DB 를 바꾸긴 힘들고, Slick 로딩부분을 수정할 방법을 찾는다. 


Upgrade Guide 에서 제공하는 링크를 참고하여 코드 수정 시작 

(참고 : https://github.com/d6y/instant-etc/blob/master/src/main/scala/main.scala#L9-L45)



import java.sql.ResultSet
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

trait NewMySQLProfile extends slick.jdbc.JdbcProfile with slick.jdbc.MySQLProfile {
  override val columnTypes = new JdbcTypes
  class JdbcTypes extends super.JdbcTypes {
    override val localDateTimeType : LocalDateTimeJdbcType = new LocalDateTimeJdbcType {
      override def getValue(r: ResultSet, idx: Int) : LocalDateTime = {
        val pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
        r.getString(idx) match {
          case null => null
          case iso8601String => LocalDateTime.parse(iso8601String, pattern)        }
      }
    }
  }
}

object NewMySQLProfile extends NewMySQLProfile


코드는 전부 올려두었으니, 분석은 이거 읽는분이 알아서... 


그리고 Slick 의 코드에 



import slick.jdbc.MySQLProfile.api._ 

이 부분을 


import NewMySQLProfile.api._ 

로 바꾸면 된다. 패키지 & 클래스명은 자기의 패키지에 맞게 수정하시길.

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

1. 빌드 툴 

 - SBT, 스칼라는 라이브러리 의존성 및 빌드를 위해 SBT 를 사용, 자세히 까지는 아니어도, 대략적으로라도 알고 있어야 함 

   https://github.com/sbt/sbt


2. Typesafe Config 

 - 스칼라에서는 환경 설정등을 읽어들이기 위해 Typesafe 를 사용함, resources 안에 application.conf 를 넣어서 관리하거나, 다른 방법으로 관리할 수 있음.

    https://github.com/lightbend/config


3. 그 밖의 라이브러리 & 프레임워크 

 - cats : 이런저런 일을 해주는 라이브러리 

 - slick : DB 접근 라이브러리 

 - play framework : 웹 서버 프레임워크

 - akka : actor 기반 작업용 프레임워크 


기본기를 잘 쌓자. typesafe 부터 반복연습

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

1. 액터 

  - 각자의 특정 작업을 수행, 각자의 Queue 를 갖고있고 dequeue 한 후 작업을 수행 


2. Supervisor 

  - 액터들의 상태를 감시하는 액터. 계층상 상위에 있는 액터는 슈퍼바이저가 된다. 


3. 테스트 

  - SilentActor -> 메세지를 받기만 하고 외부로 아예 안내보내는 액터, underlyingActor 으로 접근해 확인하거나 , expectMsg 함수호출해서 확인한다. 

  - SendingActor -> 받은 메시지를 다른 액터에 보낼 때 사용하는 방법, expectMsgPF 사용한다. 

  - SideEffectActor -> Side Effect 가 있는경우 사용 (가령 함수 내에서 로그 메시지를 출력한다거나..), 리스너를 사용할 수 있다. 


4. 내고장성 

  - 액터에 여러가지 문제가 생겼을 때, 어떻게 작동할 지 설정할 수 있다. 

  - 재시작, 종료, 진행, 위로전달 전략을 설정할 수 있으며, OneForOneStrategy 는 한개의 액터에만, AllForOneStrategy 에는 자기 휘하의 모든 액터를 종료한다.

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

코세라 스칼라 강좌 4.4 Variance 중.


Typing Rules for Functions 


If A2 <: A1 and B1 <: B2 then

    (A1 => B1) <: (A2 => B2)


뭔 소린지 전혀 몰라서 수시간을 고민해보다가 stackoverflow 보고 이해 


원문 주소는 https://stackoverflow.com/questions/41098105/subtyping-between-function-types 여기


내가 이해한 방식으로 정리한다. 


1. subtype 이다? 

  - subtype 이란건 'a 는 b 이다' 의 is - a 관계가 성립하는거.


2. (A1 => B1) <: (A2 => B2) 임을 증명하려면 

  - '(A1 => B1) 함수(이하 F1)는  (A2 => B2) 함수(이하 F2)다' 가 증명되면 됨.

    

'F1 는 F2 이다 ' 가 true 가 되려면 필요한 규칙은 


F2(a) 를 F1(a) 로 바꿔도 에러 없이 정확한 값이 나와야 한다는 소린데(리스코프의 치환 원칙), 이소리는 즉


1. F2 의 모든 input 이 F1 에서 에러가 없어야 함

2. F1 의 모든 output 이 F2 의 output 이 되어야 함


저 규칙에 맞나 보면 

1. F2 의 모든 input 이 F1 에서 에러가 없어야 함

  -> 만족한다. A2(F2 의 input) <: A1 (F1 의 input) 이기 때문에, A2 는 A1 의 부분집합이고, A2의 모든 원소는 A1 이다.

2. F1 의 모든 output 이 F2 의 output 이 되어야 함 

  -> 만족한다. B1(F1 의 output) <: B2(F2 의 output) 이기 때문에 B1 은 B2 의 부분집합이고, B1 의 모든 원소는 B2 이다.


이 때문에 'F1 은 F2 이다' 는 참이고, F1 은 F2 의 subtype 이다.


맞나 이거? 


    

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

다시 한번 복습 


A <: B 이고 C[A] <: C[B] 인걸 Covariant

A <: B 이고 C[A] >: C[B] 인걸 Contravariant


Contravariant 는 주로 함수를 파라미터로 넘길 때 일어난다고 이야기 함.


이걸 그림으로 다시 생각 해보자, 일단 아래와 같은 클래스가 있다. 


class Animal {}

class Bird extends Animal {}
class Chicken extends Bird {}
class Duck extends Bird {}


이걸 밴 다이어 그램으로 그려보면 아래와 같이 된다.



이제 함수를 선언해보자. 

object HelloWorld {
  type FunctorType[A] = (A => String) //함수타입 한개 선언

  val animalFunc : FunctorType[Animal] = { a => "Animal" }
  val birdFunc : FunctorType[Bird] = { a => "Bird" }
  val chickenFunc : FunctorType[Chicken] = { a => "Chicken"}
  val duckFunc : FunctorType[Duck] = { a => "Duck"}
}


위의 밴 다이어 그램을 보면서 다시 정리해보자 


Animal >: Bird 이면, FunctorType[Animal] 과 FunctorType[Bird] 의 관계는?


FunctorType[Animal] 을 풀어보면 Animal => String 이고, 

FunctorType[Bird] 을 풀어보면 Bird => String 인데, 

위의 벤 다이어그램에서 보면, Bird 는 Animal 의 부분집합 이니까, 모든 Bird 는 Animal 이다. 따라서, 


FunctorType[Bird]  는 Bird => String 도 될 수 있고, Animal => String 도 될 수 있다. 


때문에, FunctorType[Animal] <: FunctorType[Bird] 이 되고, 


FunctorType 은 contravariant 가 된다... 

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

코세라 강의 보는 중에 


A <: B 이고 C[A] <: C[B] 인걸 Covariant 

A <: B 이고 C[A] >: C[B] 인걸 Contravariant 라고 한다.


Covariant 는 이미 알고있는 거고,

Contravariant 가 무슨소린지 도저히 모르겠어서, 구글링... 하다가 하나 발견... 4시간에 걸쳐 겨우 이해하고, 정리글을 남긴다. 


아래와 같이 클래스들이 있고, 함수 타입 한개를 선언한다.


그리고 몇가지 함수들을 선언한다.

class Animal {}

class Bird extends Animal {}
class Chicken extends Bird {}
class Duck extends Bird {}

object HelloWorld {
  type FunctorType[A] = (A => String) //함수타입 한개 선언

  val birdFunc : FunctorType[Bird] = { a => "Bird" }
  val animalFunc : FunctorType[Animal] = { a => "Animal" }
  val chickenFunc : FunctorType[Chicken] = { a => "Chicken"}
  val duckFunc : FunctorType[Duck] = { a => "Duck"}
}


이경우 클래스 관계는 


Animal <- Bird <- Chicken

Animal <- Bird <- Duck


이런 형식으로 된다. 다들 알다시피 


하지만 함수 아래와 같이 함수가 파라미터로 전달 될 때도, 다형성이 그대로 유지될까?

def test_contravariant(a: FunctorType[Bird]): Unit = {
    val ret1 = a(new Chicken);
    println(ret1)
    val ret2 = a(new Bird);
    println(ret2)
}



test_contravariant(animalFunc) 
test_contravariant(birdFunc) 
test_contravariant(chickenFunc)  //컴파일에러
test_contravariant(duckFunc)     //컴파일에러


맨 처음 봤을때는 의외의 결과였다. 그 전에 C++ JAVA 에서 써왔던 것과 반대로 나타났기 때문이다. 


내친김에 covariant 도 테스트 해본다. 


def test_covariant(a: Bird) : Unit = {
    println(a.toString)
}


예상한대로 결과가 나온다.


test_covariant(new Animal) //컴파일에러 test_covariant(new Bird) test_covariant(new Chicken) test_covariant(new Duck)


클래스 상속과 관련해서 생각을 다시 정리해본다. 


"

슈퍼클래스의 참조로 하위클래스의 객체를 생성하거나 파라미터로 전달해도 문제가 없는 이유는, 

하위클래스라고 해서 있던 public 함수/변수가 없어지거나 하지 않기 때문이다.

"


그리고 contravariant 예제를 다시한번 본다.


val animalFunc : FunctorType[Animal] = { a => "Animal" }
val birdFunc : FunctorType[Bird] = { a => "Bird" }
val chickenFunc : FunctorType[Chicken] = { a => "Chicken"}
val duckFunc : FunctorType[Duck] = { a => "Duck"}

def test_contravariant(a: FunctorType[Bird]): Unit = {
    val ret1 = a(new Chicken);
    println(ret1)
    val ret2 = a(new Bird);
    println(ret2)
}


test_contravariant 의 파라미터로 chickenFunc 나 duckFunc 를 넘기게 되면, 


Chicken 클래스에는 있는 public 함수지만, Bird 클래스에는 없는 public 함수가 있을 수 있다.

Duck 클래스에는 있는 public 함수지만, Bird 클래스에는 없는 public 함수가 있을 수 있다.


하지만 animalFunc 을 test_contravariant 로 넘기게 되면,

Animal 클래스에 있는 모든 public 함수는 Bird 클래스에 있다.


이때문에 FunctorType[슈퍼클래스] 에 대해서는 다형성이 일어나지만,

FunctorType[하위클래스] 에 대해서는 다형성이 일어나지 않는다. 

즉, 상속 관계가 역전이 된다. 


이런 경우가 A <: B 이고 C[A] >: C[B] 이며, 

이를 contravariant 라고 부른다. 




블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

play framework로 이런저런 작업과 스칼라 관련 모임에서 코드를  보는 와중에 

 

implicit 키워드가 눈에 자주 보인다. 

 

나중에 혼란스러워 하지말고 미리 정리.

 

- implicit parameter 

(참고 : https://docs.scala-lang.org/ko/tutorials/tour/implicit-parameters.html.html

먼저 메인 클래스를 선언한다.

object HelloWorld {
  def main(args: Array[String]): Unit = {
    println("hello, world!")
  }

  def testFunction(name : String)(implicit liveObject : LiveObject) : Unit = {
    println("I am " + name)
    liveObject.introduce();
  }
}

 

간단하다, Hello, world 문자열하나만 보여준다. testFunction 은 implicit 파라미터로 LiveObject 를 사용중이다.

testFunction 은 지금 당장은 사용하지 않으니 "이런게 있구나" 수준으로 넘어가자.

 

trait의 내용은 아래와 같다. 

trait LiveObject {
  def introduce() = {
    println("I am LiveObject")
  }
}

이제 LiveObject 를 믹스인한 클래스 두 개를 생성한다.

class Animal extends LiveObject {
  override def introduce(): Unit = {
    println("I am Animal")
  }
}

class Human extends LiveObject {
  override def introduce(): Unit = {
    println("I am Human")
  }
}

 

간단하다 introduce 를 재정의 해서 문자열 하나만 다르게 재정의 했다.

 

준비작업은 모두 끝났다. 이제 main 함수 내에서 testFunction 을 호출을... 했는데? 어라? 에러가 있네?

main 함수 안에서 testFunction 호출한 모습

에러 내용을 자세히 보면 

implicit 파라미터 없음.

직접 넣어줄 수는 있다.. 근데 이건 '암시적' 아닌 것 같아 다른 방법을 찾아본다.

 

바로 위에 Animal 변수를 하나 추가해준다.. 이러면 돌아가겠지.

 


object HelloWorld {

  def main(args: Array[String]): Unit = {
    println("hello, world!")
    val animal = new Animal
    testFunction("thomas")
  }

  def testFunction(name : String)(implicit liveObject : LiveObject) : Unit = {
    println("I am " + name)
    liveObject.introduce()
  }
}

 

여전히 에러가 난다. 

 

여전히 변수를 못찾음.

암시적 파라미터 관련 스칼라 문서를 다시 읽어보면... 

 

스칼라 컴파일러는 implicit 파라미터를 만났을 때, 실제 파라미터에 할당할 변수를 검색하는데 규칙은 다음과 같다. 

1. 메서드가 호출되었을 때, prefix 없이 접근할 수 있는 변수 (당연히 변수들은 implicit 키워드가 붙어야 함.)

2. implicit 키워드가 붙은 암시적 파라미터와 관련된 모든 멤버 

 

2번은 조금 어려우니 일단 1번부터, 암시적 파라미터를 설정하려면 implicit 을 붙여야 한다. 변수를 implicit 으로 변경해서 다시.


object HelloWorld {
  def main(args: Array[String]): Unit = {
    println("hello, world!")
    implicit val animal = new Animal
    testFunction("thomas")
  }

  def testFunction(name : String)(implicit liveObject : LiveObject) : Unit = {
    println("I am " + name)
    liveObject.introduce()
  }
}

이번엔 실행이 제대로 된다.


  hello, world!
  I am thomas
  I am Animal
  Process finished with exit code 0

암시적 변수 / 함수를 선언하려면 implicit 키워드를 사용하여 선언해야 정상적으로 인식하는듯

 

그러면 이번에 같은 type 을 두 개 넣으면 어떻게 될까? 


def main(args: Array[String]): Unit = {
    println("hello, world!")
    implicit val animal = new Animal
    implicit val human = new Human
    testFunction("thomas")
}

실행 해보니 아래와 같은 Exception 이 발생한다. 

 

같은 implicit 변수가 두개 선언되었을 때 Exception

머릿속에 어느정도 그림이 그려진다. 

 

implicit 파라미터는 "함수가 호출 된 타이밍" 에 prefix 없이 접근 가능한 변수 중 "가장 가까운 변수 " 가 할당되다 라고 이해하면 빠를듯.

 

다음번엔 implicit function 을 좀 더 봐야겠다.

 

2019년 11월 18일 추가 

 

변수의 scope 가 다를경우 같은 타입의 implicit 의 선언이 가능하다. 이 경우에 가장 최근에 선언된 implicit 변수가 사용된다.

 


object HelloWorld {
  def main(args: Array[String]): Unit = {
    println("hello, world!")
    implicit val human = new Human
    overlapfunction()
  }

  def overlapfunction()(implicit liveObject : LiveObject) : Unit = {
    implicit val animal = new Animal
    testFunction("thomas")
  }

  def testFunction(name : String)(implicit liveObject : LiveObject) : Unit = {
    println("I am " + name)
    liveObject.introduce()
  }
}

위 코드를 실행하면 아래와 같은 결과가 나온다.

 

 


hello, world!
I am thomas
I am Animal
블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

다음에 다시 정리할 예정입니다.

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Amazon AWS EC2 에서(OS : Ubuntu Linux) Tomcat을 사용 하여 S3 에 접근하려 할 때, 오류 발생 


오류 정보는 


Unable to load AWS credentials from any provider in the chain 


credential 정보는 이미 ~/.aws/credential 에 있는 상태 


여러번의 시도 끝에 아래 경로에 넣으니 일단 정상 작동 


/usr/share/tomcat7/.aws/config


다 끝난줄 알았으나, 톰캣 로그에 경고 표시 


WARN : com.amazonaws.profile.path.cred.CredentialsLegacyConfigLocationProvider - Found the legacy config profiles file at [/usr/share/tomcat7/.aws/config]. Please move it to the latest default location [~/.aws/credentials].


왜이러나... 


일단 tomcat 의 HOME 이 /usr/share/tomcat7/ 로 지정되어있어서 그런 듯 보이나... 


경고 한줄 뜨는게 살짝 성가시다. 


왜 이렇게 되는지는 천천히 생각해보자.




블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

1. bootstrap 

  -  http://getbootstrap.com/

  - html 요소를 좀 더 예쁘게 만들어 줌 


2. 날짜선택 시 달력기능

  - http://www.daterangepicker.com/#options

  -  bootstrap 기반, 달력 선택 기능 제공


3. 파일 업로드 기능 

 - http://markusslima.github.io/bootstrap-filestyle/

 - 파일 업로드 태그의 커스터마이징을 도와줌

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Play framework 버전 2.5.x 에서 구현한 내용입니다. 




Play Framework 로 DB 연동할 때 주로 사용하는 도구 Slick


에러 처리 관련하여 API 문서를 뒤져 알아낸 사실들 정리


로그인 처리를 위해 Account 테이블에 쿼리를 날리는 함수 등록 


def verifyPassword[A, B](account : String, password : String) = {

  db.run {

    accounts

      .filter(record => record.account === account && record.password === password)

      .map(_.userName)

      .result

  }

}


그리고 Controller 에서 verifyPassword 호출


val loginData = request.body

ac.verifyPassword(loginData.account, loginData.password).map {

  a => if (a.nonEmpty) Ok(a.toString) else Ok("NoUser")

}


에러사항 없이 잘 돌아간다.... 근데 항상 좋은일만 생길일은 없는 법


PK 가 겹칠수도 있고

DB 서버가 맛이 가, 커넥션이 끊길수도 있고

트래픽이 많이 몰려 타임아웃이 날 수도 있고 

테이블 이름을 잘못 써 오류가 날 수도 있다.


그때마다 Play framework 는 에러를 적나라하게 보여준다. 



에러 발생 시, 적나라하게 보여준다.


 쪽팔림을 막고, 행여나 터질 보안이슈를 대비해 저런 에러들을 수정해보자.


 일단 구글에서 Slick error handling 으로 검색, 


http://slick.lightbend.com/doc/3.1.0/dbio.html  여기를 보면 아래와 같이 써있다. 


Note

For even more flexible error handling use asTry and failed. Unlike with andFinally and cleanUp the resulting actions cannot be used for streaming.


좀 더 유언하게 에러 처리를 하려면 asTry 랑 failed 를 사용해라...


일단 asTry 부터, asTry는 맨 마지막에 .asTry만 추가해주면 된다. 자세한 내용으 API 문서 참고 바람.


def verifyPassword[A, B](account : String, password : String) = {

  db.run {

    accounts

      .filter(record => record.account === account && record.password === password)

      .map(_.userName)

      .result

      .asTry

  }

}


그리고 호출부에서 결과 처리부분을 추가한다. 


val loginData = request.body

ac.verifyPassword(loginData.account, loginData.password).map {

  _ match {

    case Success(a) => if (a.nonEmpty) Ok(a.toString) else Ok("NoUser")

    case Failure(t) => Ok("Failure!!")

  }

}


테스트... 해보면 의도했던 화면들이 나온다.... 


마지막으로, 


에러처리를 위해 failed 를 쓰라고 했는데... 이건 어떻게 쓰는지 다음에 찾아봐야겠다.










블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

람다 대수 관련 자료를 찾아보면 다음과 같은 규칙이 있다. 


(x, y -> ax + by) 는 (x -> y -> ax + by) 형태로 바꿀 수 있으며 이를 커링 이라고 한다. 


Scala 를 공부하면서 가장 많이 접하는 형식인데, 지금까지 내가 쓰고있던 명령형 언어들(C/C++) 과는 형식이 많이 달라 혼란스러웠지만, 이제 머릿속으로 정리되는 듯 하여 글로 남긴다. 



대체 커링은 왜 쓰는 것인가?


Scala 공부하는 내내 '커링 이걸 대체 왜 쓰고, 어디다가 쓰는 것인가?' 란 생각이 가장 많이 들었고 오늘에서야 그 답을 찾았다. 행여나 이 글을 읽는 분께는 아래 설명이 '절대적인 정답' 이 아닌 '쓰임새 중 하나' 로 이해하시길 바란다. 


쓰임새 중 하나 : 디폴트 파라미터에 적용할 수 있다. 


우선 C++ 코드를 보자, 공간이 없어 full code 는 올리지 않겠다.

(나중에 시간이 나면 gut hub 같은데 올릴수도..)


int Adder(int num1=1, int num2=2)
{
  return num1+num2;
}


쉽게 알 수 있듯 파라미터로 넘어온 num1 과 num2 의 합을 리턴해주는 코드다.

위 C++코드엔 몇가지 제약사항이 있다. 


1. default 파라미터 뒤에, default 가 아닌 파라미터가 오면 안된다. 

  =>int Adder(int num=1, int num2 = 2, int num3) 은 안된다는 소리다(컴파일에러)

2. 매개변수가 주어지지 않을 때, default 파라미터는 왼쪽 변수부터 적용이 된다.

  => Adder(3) 으로 호출이 되었을 때 num1 의 값은 3이고, num2 의 값은 2이다.

  => num1 에는 default, num2에는 4 값을 사용토록 하려면 Adder(1,2) 같은 형태로 호출해야 한다.


Scala 에선 커링을 이용하면 이를 좀 다른 방법으로 사용할 수 있다.

(주, Scala 에서는 언어 차원으로 default parameter를 지원한다. 

링크 :  http://docs.scala-lang.org/ko/tutorials/tour/default-parameter-values )


위의 C++ 코드와 같은 기능을 하는 Scala 함수를 생각해보자 


def Adder(x : Int, y : Int) : Int = x + y


이 함수를 커링하면

def AdderDefaultX(x : Int)(y : Int) : Int = x + y

혹은

def AdderDefaultY(y : Int)(x : Int) : Int = x + y

이렇게 된다.


이후, x 값을 100 이 default  로 정해놓고 싶다면 


val AdderX100 = AdderDefaultX(100)


y 값을 100 으로 정해놓고 싶다면 


val AdderY100 = AdderDefaultY(100)


이렇게 하면 된다. 


위의 예시는 '그냥 이렇게 쓴다' 란 정도로 설명을 해 둔것이고. 


라이브러리 등의 구현(여러명이 같이 작업할 코드들)에서


'이 함수는 불려질 때 반드시 어떤 파라미터가 어떤 값이 되어야 함.' 이 호출 규칙일 때 


위 아래로 주석을 달거나, 문서화를 하기 보다는 


특정 파라미터를 특정 값으로 세팅한 함수(위의 Adder 함수들 같이)로 커링하여 외부에 노출 시킨다면 


안정성이 훨씬 증가할 듯 보인다. 


추신 : 이 글은 절대적인 글이 아니니, 수정사항이나 틀린부분 있으면 언제든지 지적을 부탁드립니다.


블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

일단 http://kr.zeromq.org/page:get-the-software 여기서 파일들을 다운로드 받는다.

(현재 stable 한 버전은 2.1, 개인의 취향에 따라 git Download 도 가능.) 


나의경우, Mac OS X  를 사용중이기 때문에, 압축파일을 통짜로 다운로드 받음.


파일들의 압축을 풀고, 라이브러리 빌드 시작... 


빌드 방법은 홈페이지에 친절하게 되어있음. 

  1. libtoolautoconfautomake가 설치되어 있는지 확인하세요
  2. uuid-dev 패키지, uuid/e2fsprogs RPM이나 이것과 동등한 것이 시스템에 설치되어 있는지 확인 합니다.
  3. .tar.gz 소스아키이브를 풀고
  4. make 명령에 의해 ./configure를 실행합니다.
  5. ØMQ시스템 전체를 설치하기 위해서 sudo make install을 실행합니다.
  6. 리눅스에서 ØMQ를 설치한 후 sudo ldconfig를 실행합니다.

이렇게 하면 usr/local/lib 에 zeromq 의 라이브러리 파일이, 

usr/local/include 에 zeromq 의 헤더파일이 생성된다. 


OS X 의 파일 시스템에 익숙치 못해 똑같은 짓을 몇번 반복... 


여튼 설치는 완료, 다음은 node.js 에서 zeromq 설치


npm install zmq.... 언제나 그렇듯 처음은 에러로 시작 


LeeHwangChunui-MacBook-Air:ZeroMQTest leehwangchun$ npm install zmq

\

> zmq@2.14.0 install /Users/leehwangchun/WebstormProjects/ZeroMQTest/node_modules/zmq

> node-gyp rebuild


/bin/sh: pkg-config: command not found

gyp: Call to 'pkg-config libzmq --libs' returned exit status 127 while in binding.gyp. while trying to load binding.gyp

gyp ERR! configure error 

gyp ERR! stack Error: `gyp` failed with exit code: 1

gyp ERR! stack     at ChildProcess.onCpExit (/usr/local/lib/node_modules/npm/node_modules/node-gyp/lib/configure.js:305:16)

gyp ERR! stack     at emitTwo (events.js:87:13)

gyp ERR! stack     at ChildProcess.emit (events.js:172:7)

gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:200:12)

gyp ERR! System Darwin 15.3.0

gyp ERR! command "/usr/local/bin/node" "/usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"

gyp ERR! cwd /Users/leehwangchun/WebstormProjects/ZeroMQTest/node_modules/zmq

gyp ERR! node -v v4.4.3

gyp ERR! node-gyp -v v3.3.1

gyp ERR! not ok 

npm ERR! Darwin 15.3.0

npm ERR! argv "/usr/local/bin/node" "/usr/local/bin/npm" "install" "zmq"

npm ERR! node v4.4.3

npm ERR! npm  v2.15.1

npm ERR! code ELIFECYCLE


npm ERR! zmq@2.14.0 install: `node-gyp rebuild`

npm ERR! Exit status 1

npm ERR! 

npm ERR! Failed at the zmq@2.14.0 install script 'node-gyp rebuild'.

npm ERR! This is most likely a problem with the zmq package,

npm ERR! not with npm itself.

npm ERR! Tell the author that this fails on your system:

npm ERR!     node-gyp rebuild

npm ERR! You can get information on how to open an issue for this project with:

npm ERR!     npm bugs zmq

npm ERR! Or if that isn't available, you can get their info via:

npm ERR! 

npm ERR!     npm owner ls zmq

npm ERR! There is likely additional logging output above.


npm ERR! Please include the following file with any support request:

npm ERR!     /Users/leehwangchun/WebstormProjects/ZeroMQTest/npm-debug.log


npm 홈페이지에 가보니 이것저것 설치가 필요하다고 한다. 


zeromq 라이브러리 설치에 필요한건 세개 


1. Zeromq 라이브러리 ( http://zeromq.org/intro:get-the-software )

2. Homebrew 패키지  ( http://brew.sh/index_ko.html )

3. Homebrew 패키지로 pkg-config 다운 (brew install pkg-config 명령어를 터미널에서 입력)


이후 Webstorm 을 켜고, npm install zmq 타이핑


\

> zmq@2.14.0 install /Users/leehwangchun/WebstormProjects/ZeroMQTest/node_modules/zmq

> node-gyp rebuild


  CXX(target) Release/obj.target/zmq/binding.o

  SOLINK_MODULE(target) Release/zmq.node

ld: warning: directory not found for option '-L/opt/local/lib'

zmq@2.14.0 node_modules/zmq

├── bindings@1.2.1

└── nan@2.0.9

 

성공이다, 이제 API 문서보고 하나씩 해나가면 된다. 

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
과연 LuaJIT 가 2~10배 정도 속도가 나온다 나온다 말만 듣다가
이제서야 테스트를 해봅니다.
결과부터 말씀드리자면 상당히 놀라운 결과였습니다.

일단 테스트용 Lua 코드는 다음과 같습니다.

function TrafficTest()
    for i = 0 , 10000, 1 do       
        iii = i * i*i*i*i*i*i*i*i*i*i*i   
    --중략, iii = i * i*i*i*i*i*i*i*i*i*i*i 와 같은 코드가 200라인 정도 있습니다.
    end
end

테스트한 C++ 코드는 다음과 같습니다.

루아 팅커를 사용하여 lua 함수를 호출하였습니다.

int _tmain(int argc, _TCHAR* argv[])
{
    lua_State *L = lua_open();   
    luaL_openlibs(L);   
    int Start, End;
    printf("LuaJIT Performance Test\n");

   
    printf("case one, LuaJIT is activated\n");
    //LuaJIT on, 디폴트 상태로 켜져있지만 명시적으로 한번 더 넣어주고 한다.
    luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE|LUAJIT_MODE_ON);
    lua_tinker::dofile(L, "TestFunction.lua");

    Start = GetTickCount();

    //루프의 수를 10, 100, 1000 순서대로 늘려가면서 복잡도를 증가시켰습니다.
    for (int i = 0; i < 10; i++)   
    {
        lua_tinker::call<void>(L, "TrafficTest");
    }   
    End = GetTickCount();
    printf("Process Time : %d\n", End - Start);

   
    //Case Two JIT off
   
    printf("case two, LuaJIT is deactivated\n");
    luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE|LUAJIT_MODE_OFF);   
    lua_tinker::dofile(L, "TestFunction.lua");

    Start = GetTickCount();
    //루프의 수를 10, 100, 1000 순서대로 늘려가면서 복잡도를 증가시켰습니다.
    for (int i = 0; i < 10; i++)   
    {
        lua_tinker::call<void>(L, "TrafficTest");
    }
    End = GetTickCount();
    printf("Process Time : %d\n", End - Start);
    return 0;
}

결과는 경이적이다 못해 충격적(일천한 제 경험 탓 일수도 있습니다만, 이게 과연 스크립트언어인가 할 정도로)입니다.

10(c 루프 횟수) x 10000(lua 루프 횟수)



100(c 루프 횟수) x 10000(lua 루프 횟수)



1000(c 루프 횟수) x 10000(lua 루프 횟수)



서버/클라이언트 클라이언트를 막론하고, Lua 를 사용하며 속도상의 이점을 가져오는게
좋을 듯 합니다.

'프로그래밍' 카테고리의 다른 글

Three.js Object Load 방법  (0) 2020.11.21
블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Visual Studio 2008 C# 컴파일러 생성 문제


아마 Visual Studio 2008을 설치 한 후, Visual Studio 2005 를 삭제했을 때, 문제가 발생하는듯합니다. 연구실에서 WPF 관련 세미나를 준비하면서 이런 현상을 겪었는데 해결책을 찾았습니다.

C# 관련 프로젝트를 열거나 새로 생성했을 때 다음과 같은 에러가 발생하면서 C# 파일이 열리지 않는데요

 

Microsoft Visual C# 2008 Compiler could not be created. QueryService for '{7D960B16-7AF8-11D0-8E5E-00A0C911005A} failed.

 

맨 처음에는 .net Framework 문제로 알고 .net Framework 삭제를 위해 동분서주 했지만 결국 실패, 구글과 MSDN 을 검색한 후 찾은 해결책 입니다.

 

1.     먼저 심호흡을 한번 크게 합니다 (긴장을 풀자고요!!)

2.     시작->프로그램->Microsoft Visual Studio 2008->Visual Studio Tools->Visual Studio 2008 Command Prompt 를 실행합니다.

3.     그러면 커맨트 창이 하나 나타나게 되는데요 거기에 다음과 같이 입력합니다.

devenv /ResetSkipPkgs

 

실행을 하게 되면 Visual Studio 2008 이 실행되면서 모든 패키지들을 초기화합니다.

이렇게 해준 후 C# 프로젝트들이 정상적으로 로딩 되는 모습을 보실 수가 있으리라 생각 됩니다.

만약 이렇게 해도 해결되지 않는다면… Visual Studio 2008 및 관련 프로그램 모두를 삭제 후 재설치 한 다음

다시 해봅니다. 그래도 되지 않는다면....

 

별수 없습니다…. 포맷 하는수밖에….

블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Visual C++ 2008의 파일읽기 버그

졸업프로젝트를 하다 이상한 현상을 목격했습니다.

이 현상 덕분에 4시간동안 아무것도 못했으니 어떻게 보면 실로 엄청난 문제같은데..

누군가에게 도움이 되고자 이렇게 만듭니다.

 

흔히 MFC 에서 파일을 읽어들일 때, CFile 을 사용합니다.

 

CFile::Read() 라는 함수를 보면 파일에 있는 내용을 읽어들이는 버퍼로 LPVOID 형 변수를 사용하는데요 보통 char 형 포인터를 캐스팅해서 사용합니다.

 

TCHAR Cost2[512];

file.Read((LPVOID)Cost2, 512);

 

MFC 에서 문자열을 위한 CString 클래스를 제공하는데 이를 사용하기 위해선

 

CString Cost2;

file.Read((LPVOID)(LPCTSTR)Cost2, file.GetLength());

 

이렇게 해줍니다. 문제는 여기서 발생합니다. CString 2개이상 선언(클래스 내부 혹은 구조체 내부 건 위치는 상관 없습니다.)해서 이를 이용해 파일 내용을 읽어들이면

사용자 삽입 이미지

위와 같은 내용을 가진 변수들이
사용자 삽입 이미지

 

이렇게 바뀝니다이런

문제는 이것 뿐만이 아닙니다.

사용자 삽입 이미지

컨트롤에 있는 변수(내용이 비어있을 것이라고 추정)는 죄다 해당 내용으로 바뀝니다. 위의 변수는 VS 2008 Feature Pack 에서 지원되는 그리드 클래스안의 변수입니다. 저렇게 되어있는 변수를 윈도우 클래스에 붙여주게 되면 당연히런타임 에러가 발생합니다.

따라서, CFile:Open 안의 데이터 내용을 문자열로 읽어들이려면

1.     TCHAR 를 사용한다.

2.     CString 을 사용하고 싶거든 실행되는 모든 객체에 대해서 파일을 읽어들인 후 일일히 초기화를 시켜줘라..(확인된 방법은 아님, 실제로 해보다가 지쳐서포기)

를 해주는게 추후 정신건강에 좋다고 생각됩니다.

2008 6 15일 박지훈씨의 의견에 의한 내용 추가

CString 의 선언 후 Debug를 통해 포인터를 조사해 보았습니다. 그 결과

사용자 삽입 이미지

CString 으로 선언된 두 변수 aa, bb 가 같은곳을 가리키고 있음을 확인할 수 있습니다.

따라서, 생각해보건데 CString 은 대입 연산이 일어날 때, 메모리를 새로 할당받아서 사용함을 생각할 수 있습니다.

 

'프로그래밍 > MFC&API' 카테고리의 다른 글

Windows Via C/C++  (0) 2008.05.21
MFC RTTI (CRuntimClass)  (2) 2008.05.20
블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
사용자 삽입 이미지

Programming Applications for Microsoft Windows 가 5 판이 나왔습니다.
그동안 지원이 중단되었던 MFC 와 운명을 같이하는것처럼 상당히 오랜만에 나왔는데요..
책을 구하다 못해 겨우 제본판으로만 구해 보고 있던 저에게는 상당한 희소식이었습니다.
교보에 6만우너 가까이 주고 해외주문을 했는데 막상 책을 받아보고 나니 돈이 전혀 아깝지가 않더라고요..
현재는 4장 프로세스 부분까지 읽고 있는데 읽으면 읽을수록 무엇인가 Windows 에 대한 비밀이  하나씩
벗겨지는 느낌입니다.
윈도우 개발자를 목표로 공부하시는 분들은 꼭 한번씩 읽어보아야 할 정도로 명작입니다.

'프로그래밍 > MFC&API' 카테고리의 다른 글

Visual C++ 2008의 파일읽기 버그  (0) 2008.06.15
MFC RTTI (CRuntimClass)  (2) 2008.05.20
블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

RTTI(Run-Time Type Information)

JAVA 와는 달리 C++ 에서는 RTTI를 완벽하게 지원하지 않습니다. 때문에 MS Compiler 차원에서 RTTI를 지원해주기 위한 방법을 구현해냈는데 그게 CRuntimeClass 와 여러가지 매크로함수를 제공하고 있습니다. 오늘은 이에대해 알아보겠습니다.

 

먼저 RTTI MFC 에서 지원하게 하려면 다음과 같은 규칙이 필요합니다.

1.     클래스는 반드시 CRuntimeClass 타입의 static 멤버를 갖고 있어야 한다.

A.     CRuntimeClass는 객체를 생성하기 전, 클래스 레벨에서 접근해야 한다
.

2.     1 에 선언된 static 멤버를 초기화하는 루틴을 구현파일에 가져야 한다.

A.     초기화 루틴은 클래스 이름, 클래스 크기를 설정하고, CRuntimeClass::m_pfnCreateObject를 클래스의 정적 멤버 함수 CreateObject 로 초기화한다.

3.     클래스는 반드시 CObject * 를 리턴하는 정적 CreateObject 함수를 선언해야 한다.

A.     클래스 레벨에서 접근해야 하므로 static 이어야 한다

4.     3 에 선언된 static 멤버 함수의 몸체를 구현파일에 가져야 한다.

A.     CreateObject 는 자기 자신을 동적으로 생성하고, 생성된 객체의 시작 주소를 리턴한다.

 

MFC 소스를 분석해 나가다보면 다음과 같은 코드를 종종 볼 수 있습니다.

RUNTIME_CLASS(CLOVE_JAENAMDoc)

DECLARE_DYNCREATE(CLOVE_JAENAMView)

IMPLEMENT_DYNCREATE(CLOVE_JAENAMView, CView)

 

그리고 다음과 같은 클래스도 볼 수 있습니다.

CRuntimeClass

 

먼저 CRuntimeClass 구조체를 살펴보면

 

struct CRuntimeClass

{

// Attributes

        LPCSTR m_lpszClassName;

        int m_nObjectSize;

        UINT m_wSchema; // schema number of the loaded class

        CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class

#ifdef _AFXDLL

        CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();

#else

        CRuntimeClass* m_pBaseClass;

#endif

 

// Operations

        CObject* CreateObject();

        BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;

 

        // dynamic name lookup and creation

        static CRuntimeClass* PASCAL FromName(LPCSTR lpszClassName);

        static CRuntimeClass* PASCAL FromName(LPCWSTR lpszClassName);

        static CObject* PASCAL CreateObject(LPCSTR lpszClassName);

        static CObject* PASCAL CreateObject(LPCWSTR lpszClassName);

 

// Implementation

        void Store(CArchive& ar) const;

        static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);

 

        // CRuntimeClass objects linked together in simple list

        CRuntimeClass* m_pNextClass;       // linked list of registered classes

        const AFX_CLASSINIT* m_pClassInit;

};

 

상당히 많은 양의 코드가 있는데 놀라지 말고 간단하게 몇가지만 살펴보고 넘어갈껍니다.

먼저 속성 부분에는 클래스 이름, 클래스 크기, 현재 스키마의 번호를 갖고 있습니다.

// Attributes

        LPCSTR m_lpszClassName; // 클래스 이름

        int m_nObjectSize; // 클래스 크기

        UINT m_wSchema; // 클래스 스키마 번호

        CObject* (PASCAL* m_pfnCreateObject)(); // 기본생성자의 함수 포인터

생성자의 함수 포인터를 가리키는게 약간 특이합니다만, 이부분에 대해서는 나중에 자세히 설명을 할 것이니, 지금은 동적 생성을 지원하기 위해 사용하는 거라고 간단히 알고 넘어갑시다.

다음은 전처리기 부분입니다.

#ifdef _AFXDLL

        CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();

#else

        CRuntimeClass* m_pBaseClass;

#endif

 

먼저 _AFXDLL 에 대해 간단하게 설명하자면, MFC 에서 빌드할 때 MFC 관련 라이브러리 (보통 AFX 로 시작하는..)를 어떻게 링크할 것인가를 물어보는 옵션이 있습니다. 동적으로 링크했을 경우는  _AFXDLL define 상태가 되고, 정적링크를 할 경우에는 _AFXDLL undefine 상태가 됩니다 따라서 동적 링크 시에는

CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();

정적 링크 시에는

CRuntimeClass* m_pBaseClass;

부분이 실행이 됩니다.

 

다음으로 함수 몇 개가 더 나옵니다.

 

// Operations

        CObject* CreateObject();

        BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;

 

먼저 RTTI를 지원하는 클래스는 CObject 에서 상속을 받아야 합니다. 그 이유는 지금 CreateObject() 함수처럼 동적으로 클래스를 생성해야 하기 때문이져… CreateObject 함수는 해당 객체를 동적으로 생성해주는 함수 입니다. 여기서 잠깐!! 지금까지의 코드는 함수의 포인터나 객체의 포인터를 사용해서 연결을 시킨 후 함수를 실행해주는데 지금의 함수 구현부는 어디있나요? 정답은 잠시후에 알 수 있을 것입니다. 잠시만 참아주세요

 

다음으로 IsDerivedFrom 함수는 인자로 받은 클래스가 현재 클래스의 상위클래스인지를 검사하는 함수입니다. 리턴값은 당연히, 상속관계면 TRUE, 아니면 FALSE 가 되겠져?

 

그 다음부분은 저도 잘 모르고…(후다닥;;;)

 

이젠 이렇게 구현해놓은 CRuntimeClass 를 실제로 어떻게 사용하는지 보겠습니다.

일단, SDI MDI MFC 프로젝트를 하나 만들어 두시고 다음을 계속 읽어내려가 주세요

저는 VS 2008 사용자라 이런저런 클래스가 많이도 생기네요

일단 2005에서도 익숙했던 도큐먼트 클래스를 보도록 하겠습니다.

헤더파일을 보면 다음과 같은 매크로 함수가 버티고 있습니다.

DECLARE_DYNCREATE(CLOVE_JAENAMDoc)

맨 처음 MFC를 접하면 수도없이 많은 매크로 함수 때문에 상당한 혼돈이 오게 되는데, 당황하지 마시고 천천히 뜯어보면 됩니다, 그러면 지금부터 저 히한하고 요상하게 생긴 매크로 함수부터 뜯어보겠습니다.

DECLARE_DYNCREATE(CLOVE_JAENAMDoc) 의 원형은 다음과 같습니다.

#define DECLARE_DYNCREATE(class_name) \

        DECLARE_DYNAMIC(class_name) \

        static CObject* PASCAL CreateObject();

 

즉 다음과 같은 매크로 함수가

DECLARE_DYNCREATE(CLOVE_JAENAMDoc)

컴파일시에는 다음과 같은 코드로 바뀝니다.

DECLARE_DYNAMIC(class_name)

static CObject* PASCAL CreateObject();

 

DECLARE_DYNAMIC(class_name) 의 원형은

 

#define DECLARE_DYNAMIC(class_name) \

protected: \

        static CRuntimeClass* PASCAL _GetBaseClass(); \

public: \

        static const CRuntimeClass class##class_name; \

        static CRuntimeClass* PASCAL GetThisClass(); \

        virtual CRuntimeClass* GetRuntimeClass() const; \

이므로 DECLARE_DYNCREATE(CLOVE_JAENAMDoc) 는 최종적으로

다음과 같은 코드로 바뀝니다

protected:

        static CRuntimeClass* PASCAL _GetBaseClass();

public:

        static const CRuntimeClass classCLOVE_JAENAMDoc;

        static CRuntimeClass* PASCAL GetThisClass();

        virtual CRuntimeClass* GetRuntimeClass() const;

           static CObject* PASCAL CreateObject();

, 그러면 여기서 RTTI를 사용하기 위한 규칙 1, 3번 을 떠올려봅시다!!

클래스는 반드시 CRuntimeClass 타입의 static 멤버를 갖고 있어야 한다

클래스는 반드시 CObject * 를 리턴하는 정적 CreateObject 함수를

선언해야 한다.

 

어때요? 참 쉽죠? (밥 아저씨가 생각나네요) 현재 클래스의 정보를 갖고있는 CRuntimeClass의 포인터를 얻어내는 함수를 제외하면 1번과 3번의 조건을 매크로 함수 하나로 처리를 해주고 있습니다

 

그러면 눈치빠른 분들은 이미 아시겠져? 다음은 무얼 할껀지

구현부분에서 어떻게 되어있는지 보겠습니다. 이제 남은 규칙은 두가지네요

 

1 에 선언된 static 멤버를 초기화하는 루틴을 구현파일에 가져야 한다.

3 에 선언된 static 멤버 함수의 몸체를 구현파일에 가져야 한다.

 

구현부분도 역시 매크로 함수 하나로 다해주고 있는게 보이네요

 

IMPLEMENT_DYNCREATE(CLOVE_JAENAMDoc, CDocument)

 

이 매크로 함수가 어떻게 정의되는지 보겠습니다.

 

#define IMPLEMENT_DYNCREATE(class_name, base_class_name) \

        CObject* PASCAL class_name::CreateObject() \

               { return new class_name; } \

        IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \

               class_name::CreateObject, NULL)

 

그리고 IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \

               class_name::CreateObject, NULL) 는 다시

 

CRuntimeClass* PASCAL class_name::_GetBaseClass() \

               { return RUNTIME_CLASS(base_class_name); } \

AFX_COMDAT const CRuntimeClass class_name::class##class_name = { \

        #class_name, sizeof(class class_name), wSchema, pfnNew, \

               &class_name::_GetBaseClass, NULL, class_init }; \

CRuntimeClass* PASCAL class_name::GetThisClass() \

        { return _RUNTIME_CLASS(class_name); } \

CRuntimeClass* class_name::GetRuntimeClass() const \

        { return _RUNTIME_CLASS(class_name); }

 

로 정의되고 있습니다. 따라서 IMPLEMENT_DYNCREATE(CLOVE_JAENAMDoc, CDocument)

CObject* PASCAL class_name::CreateObject()

        { return new CLOVE_JAENAMDoc; }

 

CRuntimeClass* PASCAL CLOVE_JAENAMDoc::_GetBaseClass()

        { return RUNTIME_CLASS(CDocument); }

 

AFX_COMDAT const CRuntimeClass CLOVE_JAENAMDoc::classCLOVE_JAENAMDoc = {

        CLOVE_JAENAMDoc, sizeof(class CLOVE_JAENAMDoc), wSchema, pfnNew,

               & CLOVE_JAENAMDoc::_GetBaseClass, NULL, class_init };

 

CRuntimeClass* PASCAL CLOVE_JAENAMDoc::GetThisClass()

        { return _RUNTIME_CLASS(CLOVE_JAENAMDoc); }

 

CRuntimeClass* CLOVE_JAENAMDoc::GetRuntimeClass() const

        { return _RUNTIME_CLASS(CLOVE_JAENAMDoc); }

 

로 다소 복잡하게 바뀌지만 헤더에서 선언한 부분을 모두 구현해주고 있습니다.

마지막으로 RUNTIME_CLASS(CLOVE_JAENAMDoc),_RUNTIME_CLASS(CLOVE_JAENAMDoc) 는 다음과

같이 정의됩니다.

 

#define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))

#ifdef _AFXDLL

#define RUNTIME_CLASS(class_name) (class_name::GetThisClass())

#else

#define RUNTIME_CLASS(class_name) _RUNTIME_CLASS(class_name)

#endif

정리하자면, _RUNTIME_CLASS(class_name) 는 현재 클래스의 이름을, 직접 리턴해주고

RUNTIME_CLASS(class_name) 은 간접적으로 _RUNTIME_CLASS(class_name) 를 호출하여 현재 클래스를 리턴해줍니다

 

이상 RTTI MFC 에서 어떻게 구현이 되어있는지 알아봤습니다.

SDI MDI 의 소스를 찬찬히 뜯어보게되면 RUNTIME_CLASS 같은 매크로 함수는 많이 보게 되니 이해하시는데 도움이 되었으면 좋겠습니다.

'프로그래밍 > MFC&API' 카테고리의 다른 글

Visual C++ 2008의 파일읽기 버그  (0) 2008.06.15
Windows Via C/C++  (0) 2008.05.21
블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,