ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • chain에 대한 나만의 구현
    AI 2024. 7. 12. 22:07

     

     

     

     

    # 본문

    랭체인을 공부하다가 LCEL이라는 내용을 보게 되었는데, LCEL 안에 chain과 관련된 코드를 보고 타입 유추가 잘 안돼서 나만의 방식을 고민해 보았다.

    from typing import TypeVar, Generic
    
    Input = TypeVar('Input')
    Output = TypeVar('Output')
    Mid = TypeVar('Mid')
    NextOutput = TypeVar('NextOutput')
    
    class RawConverter (Generic[Input, Output]):
      def convert (self, input: Input) -> Output:
        return input
    
    class Converter (RawConverter[Input, Output]):
      def __or__ (self, next_converter: RawConverter[Output, NextOutput]):
        return CombinedConverter[Input, NextOutput, Output](self, next_converter)
    
    class CombinedConverter (Converter[Input, Output], Generic[Input, Mid, Output]):
      def __init__ (self, input_side_converter: Converter[Input, Mid], output_side_converter: Converter[Mid, Output]):
        self.input_side_converter = input_side_converter
        self.output_side_converter = output_side_converter
    
      def convert (self, input: Input) -> Output:
        return self.output_side_converter.convert(self.input_side_converter.convert(input))

     

    from converter import Converter
    
    class Converter1 (Converter[int, str]):
      def convert (self, input: int):
        return str(input * 2)
    
    class Converter2 (Converter[str, str]):
      def convert (self, input: str):
        return input * 2
    
    converter = Converter1() | Converter2()
    
    converter.convert(3) # -> '66'

     

    내 생각인데, Generic으로는 다형성은 지양해야 되지 않을까 싶다.

    Generic으로 다형성을 만든다는 것은 다음과 같은 상황을 의미한다.

    (LECL의 __or__ 연산자 오버로딩이 이런 식으로 구현되어 있다.)

    from typing import Generic, TypeVar, Any
    
    Input = TypeVar('Input')
    Output = TypeVar('Output')
    
    class Converter (Generic[Input, Output]):
      def convert (self, input: Input) -> Output:
        pass
    
    class Converter1 (Converter[int, str]):
      def convert (self, input: int):
        return str(input * 2)
    
    class Converter2 (Converter[str, str]):
      def convert (self, input: str):
        return input * 2
    
    converter: Converter[Any, Any]
    
    converter = Converter1()
    converter = Converter2()

     

    즉 Generic으로 생성될 모든 Converter class들에 대한 다형성을 갖는 어떤 타입을 만드는 것을 말한다.

    문제는 이렇게 하면 지금 내가 관심이 있는 이 converter의 Input과 Output 타입이 무엇인지 알기가 힘든 상황이 찾아올 수 있다는 것이다. 즉 가독성을 떨어 뜨린다.

     

    # 사담

    사실 나는 Any타입이나 type operation도 별로 안 좋아한다.

    왠만하면 Any타입은 귀찮을 때만 사용하고 (귀찮은 건 어쩔 수 없지...)

    type operation은 다형성을 만들지 않을 때만 사용하는 게 좋지 않을까?

    (대표적인 예는 간단한 자료구조들에만 사용하는 것.)

    (type operation을 class에 사용하는 것은 손해가 더 많다고 생각한다.)

     

    다만, 코딩 스타일에는 정답이 없다는 것을 알아줬으면 좋겠다.

    "# 사담"은 그냥 나의 코딩스타일을 완성하기 위해 나에게 내리는 매뉴얼 정도이지 타인에게 강요되어서는 안된다.

     

     

    # 생각 변화

    Any type이 cpp의 (void *)같은 곳에서 온 자연스러운 것 이라고 생각이 들었다.

    사실상 Any type을 쓰지 않으면 구현할 수 없는 몇 가지 어플리케이션들이 떠올랐다.

    Generic에 의한 다형성도 허용해야 한다고 생각이 들었다.

    내가 너무 모든 에러를 compile time에서 type 추론으로 미리 예방하고자 했던 거 같다.

    하지만 어떤 에러들은 런타임에 발생할 수 밖에 없음을 깨달았다.

    런타임에서 에러를 처리하는 로직을 개발하는 것이 최선인 경우도 있다.

    댓글

Designed by Tistory.