펄의“축복”은 정확히 무엇을 하는가?


142

클래스의 "새"메소드 내에서 Perl에서 "축복"키워드를 사용하는 것으로 알고 있습니다.

sub new {
    my $self = bless { };
    return $self;
}    

그러나 해시 참조에 정확히 "축복"을하는 것은 무엇입니까?


2
1999 년부터 "내 추천자 축복"을 참조하십시오 . 매우 상세하게 보입니다. ( Perl 수동 입력 은 불행히도 그것에 대해 말할 것이 많지 않습니다.)
Jon Skeet

답변:


143

일반적으로 bless객체를 클래스와 연결합니다.

package MyClass;
my $object = { };
bless $object, "MyClass";

이제 on 메소드를 호출하면 $objectPerl은 메소드를 검색 할 패키지를 알고 있습니다.

예제에서와 같이 두 번째 인수를 생략하면 현재 패키지 / 클래스가 사용됩니다.

명확성을 기하기 위해 예제는 다음과 같이 작성 될 수 있습니다.

sub new { 
  my $class = shift; 
  my $self = { }; 
  bless $self, $class; 
} 

편집 : 좀 더 자세한 내용은 kixx 의 좋은 답변 을 참조하십시오 .


79

bless 참조를 패키지와 연관시킵니다.

참조가 무엇인지는 중요하지 않습니다. 해시 (가장 일반적인 경우), 배열 (그렇지 않은), 스칼라 (일반적으로 이것은 내부 객체를 나타냄), 정규 표현식에 대한 것일 수 있습니다 , 서브 루틴 또는 TYPEGLOB ( 유용한 예제는 Object Oriented Perl : Damian Conway의 개념 및 프로그래밍 기술에 대한 종합 안내서 참조) 또는 파일 또는 디렉토리 핸들에 대한 참조 (최소 공통 사례)를 참조하십시오.

그 효과 bless는 복된 참조에 특별한 구문을 적용 할 수 있다는 것입니다.

예를 들어, 복된 참조가 저장되어 있으면 $obj( bless"클래스"패키지와 연관 됨 ) $obj->foo(@args)서브 루틴을 호출하고 foo첫 번째 인수를 참조 $obj다음에 나머지 인수 ( @args)가 전달합니다. 서브 루틴은 "클래스"패키지에 정의되어야합니다. foo"클래스"패키지에 서브 루틴이 없으면 다른 패키지 목록 ( @ISA"클래스"패키지 의 배열 에서 가져온 )이 검색되고 foo발견 된 첫 번째 서브 루틴 이 호출됩니다.


16
초기 진술이 잘못되었습니다. 그렇습니다. 축복은 참조를 첫 번째 인수로 사용하지만 참조 자체가 아니라 축복받은 참조 변수입니다. $ perl -le 'sub Somepackage :: foo {42}; % h = (); $ h = \ % h; $ h, "Somepackage"를 축복하십시오. $ j = \ % h; print $ j-> UNIVERSAL :: can ( "foo")-> ()
converter42

1
Kixx의 설명은 포괄적입니다. 우리는 이론적 인 축소에 대한 변환기의 선택을 방해해서는 안됩니다.
축복받은

19
@Blessed Geek, 이론적 인 세부 사항이 아닙니다. 차이점은 실용적인 응용 프로그램입니다.
ikegami 2016 년

3
"내부 객체"에 대한 오래된 perlfoundation.org 링크는 기껏해야 로그인 벽 뒤에 있습니다. 원본의 Archive.org 링크는 여기에 있습니다 .
ruffin

2
아마도 이것은 @harmic에 대한 깨진 링크 대신에 제공 될 것입니다 : perldoc.perl.org/perlobj.html#Inside-Out-objects
Rhubbarb

9

짧은 버전 : 해시를 현재 패키지 네임 스페이스에 첨부 된 것으로 표시하여 패키지가 클래스 구현을 제공합니다.


7

이 함수는 REF가 참조하는 엔티티에게 CLASSNAME 패키지의 오브젝트이거나 CLASSNAME이 생략 된 경우 현재 패키지임을 알려줍니다. 2 인수 형식의 축복을 사용하는 것이 좋습니다.

:

bless REF, CLASSNAME
bless REF

반환 값

이 함수는 CLASSNAME에 축복 된 객체에 대한 참조를 반환합니다.

:

다음은 기본 사용법을 보여주는 예제 코드입니다. 패키지 클래스에 대한 참조를 축복하여 객체 참조를 만듭니다.

#!/usr/bin/perl

package Person;
sub new
{
    my $class = shift;
    my $self = {
        _firstName => shift,
        _lastName  => shift,
        _ssn       => shift,
    };
    # Print all the values just for clarification.
    print "First Name is $self->{_firstName}\n";
    print "Last Name is $self->{_lastName}\n";
    print "SSN is $self->{_ssn}\n";
    bless $self, $class;
    return $self;
}

4

여기에있는 사람들이 나를 위해 클릭하지 않았기 때문에 여기에 답변을 드리겠습니다.

Perl의 bless 함수는 모든 참조를 패키지 내의 모든 함수와 연관시킵니다.

왜 이것이 필요할까요?

JavaScript로 예제를 표현해 봅시다 :

(() => {
    'use strict';

    class Animal {
        constructor(args) {
            this.name = args.name;
            this.sound = args.sound;
        }
    }

    /* [WRONG] (global scope corruption)
     * var animal = Animal({
     *     'name': 'Jeff',
     *     'sound': 'bark'
     * }); 
     * console.log(animal.name + ', ' + animal.sound); // seems good
     * console.log(window.name); // my window's name is Jeff?
     */

    // new is important!
    var animal = new Animal(
        'name': 'Jeff',   
        'sound': 'bark'
    );

    console.log(animal.name + ', ' + animal.sound); // still fine.
    console.log(window.name); // undefined
})();

이제 클래스 구성을 제거하고 그것없이 할 수 있습니다.

(() => {
    'use strict';

    var Animal = function(args) {
        this.name = args.name;
        this.sound = args.sound;
        return this; // implicit context hashmap
    };

    // the "new" causes the Animal to be unbound from global context, and 
    // rebinds it to an empty hash map before being constructed. The state is
    // now bound to animal, not the global scope.
    var animal = new Animal({
        'name': 'Jeff',
        'sound': 'bark'
    });
    console.log(animal.sound);    
})();

이 함수는 정렬되지 않은 속성의 해시 테이블을 가져옵니다 (2016 년에 동적 언어로 특정 순서로 속성을 작성 해야하는 것은 의미가 없으므로). 해당 속성이있는 해시 테이블을 반환하거나 새 키워드를 잊어 버린 경우 전체 전역 컨텍스트를 반환합니다 (예 : 브라우저의 창 또는 nodejs의 전역).

Perl에는 "this"나 "new", "class"가 없지만 여전히 비슷한 기능을 수행 할 수 있습니다. 우리에게는 생성 자나 프로토 타입이 없지만, 원하는대로 새 동물을 만들고 개별 속성을 수정할 수 있습니다.

# self contained scope 
(sub {
    my $Animal = (sub {
        return {
            'name' => $_[0]{'name'},
            'sound' => $_[0]{'sound'}
        };
    });

    my $animal = $Animal->({
        'name' => 'Jeff',
        'sound' => 'bark'
    });

    print $animal->{sound};
})->();

이제 우리는 문제가 있습니다 : 우리가 동물의 목소리를 인쇄하는 대신 동물이 스스로 소리를 내도록하려면 어떻게해야합니까? 즉, 동물의 소리를 출력하는 performSound 함수가 필요합니다.

이를 수행하는 한 가지 방법은 각 동물에게 소리를내는 방법을 가르치는 것입니다. 즉, 각 Cat에는 소리를 수행하기위한 고유 한 복제 기능이 있습니다.

# self contained scope 
(sub {
    my $Animal = (sub {
        $name = $_[0]{'name'};
        $sound = $_[0]{'sound'};

        return {
            'name' => $name,
            'sound' => $sound,
            'performSound' => sub {
                print $sound . "\n";
            }
        };
    });

    my $animal = $Animal->({
        'name' => 'Jeff',
        'sound' => 'bark'
    });

    $animal->{'performSound'}();
})->();

동물을 만들 때마다 performSound가 완전히 새로운 함수 객체로 지정되기 때문에 이것은 나쁩니다. 10000 동물은 10000 PerformSounds를 의미합니다. 우리는 그들 자신의 소리를 찾아서 인쇄하는 모든 동물들이 사용하는 단일 함수 performSound를 원합니다.

(() => {
    'use strict';

    /* a function that creates an Animal constructor which can be used to create animals */
    var Animal = (() => {
        /* function is important, as fat arrow does not have "this" and will not be bound to Animal. */
        var InnerAnimal = function(args) {
            this.name = args.name;
            this.sound = args.sound;
        };
        /* defined once and all animals use the same single function call */
        InnerAnimal.prototype.performSound = function() {
            console.log(this.name);
        };

        return InnerAnimal;
    })();

    /* we're gonna create an animal with arguments in different order
       because we want to be edgy. */
    var animal = new Animal({
        'sound': 'bark',
        'name': 'Jeff'
    });
    animal.performSound(); // Jeff
})();

여기에 Perl 종류와의 평행이 멈추는 곳이 있습니다.

JavaScript의 새로운 연산자는 선택 사항이 아니며 객체 메소드 내부의 "this"가 전역 범위를 손상시킵니다.

(() => {
    // 'use strict'; // uncommenting this prevents corruption and raises an error instead.

    var Person = function() {
        this.name = "Sam";
    };
//    var wrong = Person(); // oops! we have overwritten window.name or global.main.
//    console.log(window.name); // my window's name is Sam?
    var correct = new Person; // person's name is actually stored in the person now.

})();

우리는 건설 할 때 하드 코딩하지 않고 그 동물의 소리를 찾는 각 동물에 대해 하나의 기능을 원합니다.

축복을 통해 패키지를 객체의 프로토 타입으로 사용할 수 있습니다. 이런 방식으로 객체는 "참조 된" "패키지"를 인식하고 패키지의 함수가 해당 "패키지 객체"의 생성자에서 생성 된 특정 인스턴스에 "도달"할 수 있습니다.

package Animal;
sub new {
    my $packageRef = $_[0];
    my $name = $_[1]->{'name'};
    my $sound = $_[1]->{'sound'};

    my $this = {
        'name' => $name,
        'sound' => $sound
    };   

    bless($this, $packageRef);
    return $this;
}

# all animals use the same performSound to look up their sound.
sub performSound {
    my $this = shift;
    my $sound = $this->{'sound'};
    print $sound . "\n";
}

package main;
my $animal = Animal->new({
    'name' => 'Cat',
    'sound' => 'meow'
});
$animal->performSound();

요약 / TL; DR :

Perl에는 "this", "class"또는 "new"가 없습니다. 패키지에 객체를 축복하면 해당 객체에 패키지에 대한 참조가 제공되고 패키지에서 함수를 호출 할 때 인수가 1 슬롯만큼 오프셋되고 첫 번째 인수 ($ _ [0] 또는 shift)는 다음과 같습니다. 자바 스크립트의 "this". 결과적으로 JavaScript 프로토 타입 모델을 다소 시뮬레이션 할 수 있습니다.

불행히도 런타임에 "새 클래스"를 만드는 것은 불가능합니다. 각 "클래스"에는 자체 패키지가 있어야하지만 자바 스크립트에서는 패키지가 전혀 필요하지 않습니다. 새로운 함수를 추가하고 즉시 함수를 제거 할 수있는 런타임에 패키지로 사용할 수 있도록 익명의 해시 맵을 구성합니다.

무스 (Mose)와 같은 표현성의 한계를 극복하는 고유 한 방식을 만드는 일부 Perl 라이브러리가 있습니다.

왜 혼란? :

패키지로 인해. 직관은 객체를 프로토 타입이 포함 된 해시 맵에 바인딩하도록 지시합니다. 이를 통해 JavaScript와 같이 런타임에 "패키지"를 만들 수 있습니다. Perl은 그러한 유연성을 갖지 못하고 (적어도 내장되어 있지 않거나, 그것을 개발하거나 다른 모듈에서 가져와야 함) 런타임 표현력이 저해됩니다. 그것을 "축복"이라고 부르는 것도 그다지 호의적이지 않습니다.

우리가하고 싶은 것 :

이와 같은 것이지만 프로토 타입 맵을 재귀 적으로 바인딩하고 명시 적으로 수행하지 않고 암시 적으로 프로토 타입에 바인딩해야합니다.

여기에는 순진한 시도가 있습니다. 문제는 "호출"이 "호출"을 모르기 때문에 객체에 메소드가 있는지 확인하는 범용 perl 함수 "objectInvokeMethod (object, method)"일 수도 있습니다. 또는 프로토 타입이 있거나 끝에 도달하여 찾을 수있을 때까지 (프로토 타입 상속) 프로토 타입이 있습니다. Perl은 훌륭한 평가 마법을 가지고 있지만 나중에 시도해 볼 수있는 것을 남겨 두겠습니다.

어쨌든 여기 아이디어가 있습니다.

(sub {

    my $Animal = (sub {
        my $AnimalPrototype = {
            'performSound' => sub {
                return $_[0]->{'sound'};
            }
        };

        my $call = sub {
            my $this = $_[0];
            my $proc = $_[1];

            if (exists $this->{$proc}) {
                return $this->{$proc}->();
            } else {
                return $this->{prototype}->{$proc}->($this, $proc);
            }
        };

        return sub {
            my $name = $_[0]->{name};
            my $sound = $_[0]->{sound};

            my $this = { 
                'this' => $this,
                'name' => $name,
                'sound' => $sound,
                'prototype' => $AnimalPrototype,
                'call' => $call                
            };
        };
    })->();

    my $animal = $Animal->({
        'name' => 'Jeff',
        'sound'=> 'bark'
    });
    print($animal->{call}($animal, 'performSound'));
})->();

어쨌든 누군가이 게시물이 유용하다는 것을 알기를 바랍니다.


런타임에 새 클래스를 작성하는 것은 불가능하지 않습니다. 수업에 my $o = bless {}, $anything;물건을 축복합니다 $anything. 마찬가지로 {no strict 'refs'; *{$anything . '::somesub'} = sub {my $self = shift; return $self->{count}++};라는 클래스에 'somesub'라는 메서드를 만듭니다 $anything. 이것은 모두 런타임에 가능합니다. 그러나 "가능한 것"은 매일 코드를 사용하는 것이 좋은 습관이 아닙니다. 그러나 Moose 또는 Moo와 같은 객체 오버레이 시스템을 구축하는 데 유용합니다.
DavidO

흥미로운 점은 런타임에 이름이 결정되는 클래스에 참조자를 축복 할 수 있다고 말하는 것입니다. 흥미로워 보이고 내 unfortunately it makes it impossible(to my understanding) to create "new classes" at runtime주장을 무효화 합니다. 필자는 런타임에 패키지 시스템을 조작 / 내부 조사하는 것이 훨씬 직관적이지 않은 것으로 결론을 내렸지 만 지금까지 본질적으로 할 수없는 것은 표시하지 못했습니다. 패키지 시스템은 런타임에 자체를 추가 / 제거 / 검사 / 수정하는 데 필요한 모든 도구를 지원하는 것 같습니다.
Dmitry

이것은 맞습니다. 프로그래밍 방식으로 Perl의 기호 테이블을 조작 할 수 있으므로 어디에서나 "package Foo"를 선언하지 않아도 런타임에 Perl의 패키지 및 패키지 멤버를 조작 할 수 있습니다. 런타임시 심볼 테이블 검사 및 조작, AUTOLOAD 시맨틱, 서브 루틴 속성, 변수를 클래스에 연결하는 등 여러 가지 방법이 있습니다. 그중 일부는 API 자동 생성, 유효성 검사 도구, 자동 문서화 API에 유용합니다. 모든 사용 사례를 예측할 수는 없습니다. 발로 자신을 촬영하는 것도 그러한 속임수의 가능한 결과입니다.
DavidO

4

많은 좋은 답변과 함께 bless-ed 참조를 구체적으로 구별하는 SV 것은 추가 FLAGS( OBJECT) 및STASH

perl -MDevel::Peek -wE'
    package Pack  { sub func { return { a=>1 } } }; 
    package Class { sub new  { return bless { A=>10 } } }; 
    $vp  = Pack::func(); print Dump $vp;   say"---"; 
    $obj = Class->new;   print Dump $obj'

동일하고 관련이없는 부품이 억제 된 인쇄

SV = 0x12d5540에서 IV (0x12d5530)
  REFCNT = 1
  플래그 = (ROK)
  RV = 0x12a5a68
  SV = 0x12a5a68에서 PVHV (0x12ab980)
    REFCNT = 1
    플래그 = (SHAREKEYS)
    ...
      SV = 0x12a5cf0에서 IV (0x12a5ce0)
      REFCNT = 1
      플래그 = (IOK, pIOK)
      IV = 1
---
SV = 0x12cb8c8에서 IV (0x12cb8b8)
  REFCNT = 1
  플래그 = (PADMY, ROK)
  RV = 0x12c26b0
  SV = 0x12c26b0에서 PVHV (0x12aba00)
    REFCNT = 1
    플래그 = (OBJECT, SHAREKEYS)
    STASH = 0x12d5300 "클래스"
    ...
      SV = 0x12c26c8에서 IV (0x12c26b8)
      REFCNT = 1
      플래그 = (IOK, pIOK)
      IV = 10

그것으로 1) 그것은 객체 2) 그것이 속한 패키지라는 것이 알려져 있으며, 이것은 그것의 사용을 알려줍니다.

예를 들어, 해당 변수에 대한 참조 해제가 발생하면 ( $obj->name) 패키지 또는 계층 구조에서 해당 이름을 가진 하위 항목을 찾고 객체가 첫 번째 인수 등으로 전달됩니다.


1

이 생각에 따라 개발 객체 지향 Perl을 안내합니다.

모든 데이터 구조 참조를 클래스와 연관시킵니다. Perl이 상속 구조 (일종의 트리에서)를 작성하는 방법을 고려하면 오브젝트 모델을 사용하여 컴포지션 할 오브젝트를 쉽게 작성할 수 있습니다.

이 연관성을 위해 우리는 객체라고 불렀습니다. 개발하기 위해서는 항상 객체의 내부 상태와 클래스 동작이 분리되어 있음을 명심하십시오. 또한 모든 패키지 / 클래스 동작을 사용하도록 데이터 참조를 축복 / 허용 할 수 있습니다. 패키지는 대상의 "감성적"상태를 이해할 수 있기 때문입니다.


다음은 Perl이 패키지 네임 스페이스와 작동하는 방식과 네임 스페이스에 등록 된 상태와 작동하는 방식에 대한 동일한 공지입니다. 이것이 use namespace :: clean과 같은 실용성이 있기 때문에. 그러나 일을 더 단순하게 유지하십시오.
Steven Koch

-9

예를 들어, 어떤 Bug 객체가 축복 된 해시가 될 것이라고 확신 할 수 있다면 Bug :: print_me 메소드에서 누락 된 코드를 (마침내!) 채울 수 있습니다.

 package Bug;
 sub print_me
 {
     my ($self) = @_;
     print "ID: $self->{id}\n";
     print "$self->{descr}\n";
     print "(Note: problem is fatal)\n" if $self->{type} eq "fatal";
 }

이제 print_me 메소드가 Bug 클래스에 축복 된 해시에 대한 참조를 통해 호출 될 때마다 $ self 변수는 첫 번째 인수로 전달 된 참조를 추출한 다음 print 문은 축복받은 해시의 다양한 항목에 액세스합니다.


@darch이 답변은 어떤 출처에서 표절 되었습니까?
앤더슨 그린
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.