Julia: 추상 유형에 대한 인터페이스

에 만든 2014년 05월 26일  ·  171코멘트  ·  출처: JuliaLang/julia

이 기능 요청은 예를 들어 #5에서 논의되었지만 아직 자체 문제가 없다고 생각합니다.

추상 유형에 대한 인터페이스를 명시적으로 정의할 수 있다면 좋을 것 같습니다. 인터페이스란 추상 유형 요구 사항을 충족하기 위해 구현해야 하는 모든 메서드를 의미합니다. 현재 인터페이스는 암시적으로만 정의되어 있으며 여러 파일에 흩어져 있을 수 있으므로 추상 유형에서 파생할 때 구현해야 할 항목을 결정하기가 매우 어렵습니다.

인터페이스는 기본적으로 두 가지를 제공합니다.

  • 한 곳에서 인터페이스의 자체 문서화
  • 더 나은 오류 메시지

Base.graphics에는 대체 구현에서 오류 메시지를 인코딩하여 실제로 인터페이스를 정의할 수 있는 매크로가 있습니다. 나는 이것이 이미 매우 영리하다고 생각합니다. 그러나 다음 구문을 제공하는 것이 훨씬 더 깔끔합니다.

abstract MyType has print, size(::MyType,::Int), push!

여기에서 다른 세분성을 지정할 수 있다면 깔끔할 것입니다. printpush! 선언은 해당 이름(및 MyType 을 첫 번째 매개변수로)을 가진 메서드가 있어야 한다고만 말하고 유형을 지정하지는 않습니다. 대조적으로 size 선언은 완전히 형식화됩니다. 이것은 많은 유연성을 제공하고 형식화되지 않은 인터페이스 선언에 대해 여전히 매우 구체적인 오류 메시지를 줄 수 있다고 생각합니다.

#5에서 말했듯이 이러한 인터페이스는 기본적으로 C++14 또는 C++17에 대해 Concept-light 로 C++에서 계획된 것입니다. 그리고 꽤 많은 C++ 템플릿 프로그래밍을 해본 결과 이 ​​영역의 일부 형식화가 Julia에게도 도움이 될 것이라고 확신합니다.

가장 유용한 댓글

구체적이지 않은 아이디어와 관련 배경 작업에 대한 링크에 대한 토론을 위해서는 해당 담론 스레드를 시작하여 게시하고 토론하는 것이 좋습니다.

정적으로 유형이 지정된 언어의 일반 프로그래밍에 대한 연구에서 발생하고 논의된 거의 모든 문제는 Julia와 관련이 없습니다. 정적 언어는 원하는 코드를 작성할 수 있는 충분한 표현력을 제공하는 동시에 유형 시스템 위반이 없는지 정적으로 유형 검사할 수 있는 문제에 거의 전적으로 관심이 있습니다. 표현력에 문제가 없고 정적 유형 검사가 필요하지 않으므로 Julia에서는 그 중 어느 것도 실제로 중요하지 않습니다.

우리가 관심을 갖는 것은 사람들이 프로토콜의 기대치를 구조화된 방식으로 문서화하여 언어가 동적으로 확인할 수 있도록 하는 것입니다(가능한 경우 사전에). 우리는 또한 사람들이 특성과 같은 것에 대해 파견할 수 있도록 하는 데에도 신경을 쓰고 있습니다. 연결해야 하는지 여부는 열려 있습니다.

결론: 정적 언어로 된 프로토콜에 대한 학문적 연구는 일반적으로 관심이 있을 수 있지만 Julia의 맥락에서는 별로 도움이 되지 않습니다.

모든 171 댓글

일반적으로 이것이 더 나은 인터페이스 지향 프로그래밍을 위한 좋은 방향이라고 생각합니다.

그러나 여기에 누락된 것이 있습니다. 메서드의 서명(이름뿐 아니라)도 인터페이스에 중요합니다.

이것은 구현하기 쉬운 것이 아니며 많은 문제가 있을 것입니다. 이것이 아마도 _Concepts_가 C++ 11에서 허용되지 않은 이유 중 하나일 것입니다. 그리고 3년 후에는 매우 제한된 _lite_ 버전만 C++ 14에 포함됩니다.

내 예제의 size 메서드에는 서명이 포함되어 있습니다. Base.graphics의 추가 @mustimplement 도 서명을 고려합니다.

유형을 특정 추상 유형의 하위 유형으로 제한하는 기능인 Concept-light 의 한 부분이 이미 있음을 추가해야 합니다. 인터페이스는 다른 부분입니다.

그 매크로는 꽤 멋집니다. 수동으로 오류를 유발하는 대체를 정의했으며 인터페이스를 정의하는 데 꽤 잘 작동했습니다. 예를 들어 JuliaOpt의 MathProgBase가 이 작업을 수행하며 잘 작동합니다. 나는 새로운 솔버(https://github.com/IainNZ/RationalSimplex.jl)를 가지고 놀고 있었고, 작동을 위해 오류 발생을 멈출 때까지 인터페이스 기능을 계속 구현해야 했습니다.

당신의 제안도 비슷한 일을 할 것입니다. 맞죠? 하지만 전체 인터페이스를 구현 _해야_ 하시겠습니까?

이것은 공변/반공 매개변수를 어떻게 처리합니까?

예를 들어,

abstract A has foo(::A, ::Array)

type B <: A 
    ...
end

type C <: A
    ...
end

# is it ok to let the arguments to have more general types?
foo(x::Union(B, C), y::AbstractArray) = ....

@IainNZ 예, 제안은 실제로 @mustimplement 를 좀 더 다양하게 만드는 것입니다. 예를 들어 서명은 제공할 수 있지만 제공할 필요는 없습니다. 그리고 내 느낌은 이것이 자체 구문을 얻을 가치가 있는 "핵심"이라는 것입니다. 모든 메서드가 실제로 구현되도록 하는 것이 좋지만 @mustimplement 에서 수행되는 현재 런타임 검사는 이미 훌륭하고 구현하기 더 쉬울 수 있습니다.

@lindahua 흥미로운 예입니다. 그것에 대해 생각해야합니다.

@lindahua One은 아마도 귀하의 예가 제대로 작동하기를 원할 것입니다. @mustimplement 는 보다 구체적인 메서드 서명을 정의하므로 작동하지 않습니다.

따라서 이것은 컴파일러에서 조금 더 깊이 구현해야 할 수도 있습니다. 추상 유형 정의에서 인터페이스 이름/서명을 추적해야 합니다. 그리고 현재 "... 정의되지 않음" 오류가 발생하는 지점에서 적절한 오류 메시지를 생성해야 합니다.

정보를 표현하고 액세스하는 구문과 API가 있는 경우 MethodError 인쇄 방법을 변경하는 것은 매우 쉽습니다.

이것이 우리에게 얻을 수 있는 또 다른 것은 유형(모든 유형?)이 상위 유형의 인터페이스를 완전히 구현하는지 확인하는 base.Test 의 함수입니다. 정말 깔끔한 단위 테스트가 될 것입니다.

@ivarne님 감사합니다. 따라서 구현은 다음과 같이 보일 수 있습니다.

  1. 하나는 추상 유형을 키로 사용하고 함수(+ 선택적 서명)를 값으로 사용하는 전역 사전이 있습니다.
  2. has 선언이 구문 분석될 때 dict를 채우도록 구문 분석기를 조정해야 합니다.
  3. MethodError 는 현재 함수가 전역 사전의 일부인지 조회해야 합니다.

그러면 대부분의 논리가 MethodError 있습니다.

나는 이것으로 약간의 실험을 해왔고 다음 요점을 사용하여 https://gist.github.com/tknopp/ed53dc22b61062a2b283 내가 할 수 있습니다.

julia> abstract A
julia> addInterface(A,length)
julia> type B <: A end
julia> checkInterface(B)
ERROR: Interface not implemented! B has to implement length in order to be subtype of A ! in error at error.jl:22

length 정의할 때 오류가 발생하지 않습니다.

julia> import Base.length
julia> length(::B) = 10
length (generic function with 34 methods)
julia> checkInterface(B)
true

이것이 현재 서명을 고려하지 않는다는 것은 아닙니다.

함수 서명을 고려할 수 있도록 요점의 코드를 약간 업데이트했습니다. 그것은 여전히 ​​매우 해키하지만 다음은 이제 작동합니다.

julia> abstract A
julia> type B <: A end

julia> addInterface(A,:size,(A,Int64))
1-element Array{(DataType,DataType),1}:
 (A,Int64)
julia> checkInterface(B)
ERROR: Interface not implemented! B has to implement size in order to be subtype of A !
in error at error.jl:22

julia> import Base.size
julia> size(::B, ::Integer) = 333
size (generic function with 47 methods)
julia> checkInterface(B)
true

julia> addInterface(A,:size,(A,Float64))
2-element Array{(DataType,DataType),1}:
 (A,Int64)
 (A,Float64)
julia> checkInterface(B)
ERROR: Interface not implemented! B has to implement size in order to be subtype of A !
 in error at error.jl:22
 in string at string.jl:30

요점의 인터페이스 캐시는 이제 함수 대신 기호에서 작동하므로 나중에 인터페이스를 추가하고 함수를 선언할 수 있다고 추가해야 합니다. 서명도 마찬가지일 것입니다.

방금 #2248에 인터페이스에 대한 자료가 있다는 것을 알았습니다.

0.3 버전이 나올 때까지 인터페이스와 같은 더 추측적인 기능에 대한 생각을 게시하는 것을 보류하려고 했지만 토론을 시작한 이후로 얼마 전에 작성한 내용이 있습니다.


다음은 인터페이스 선언 및 해당 인터페이스의 구현을 위한 구문 모형입니다.

interface Iterable{T,S}
    start :: Iterable --> S
    done  :: (Iterable,S) --> Bool
    next  :: (Iterable,S) --> (T,S)
end

implement UnitRange{T} <: Iterable{T,T}
    start(r::UnitRange) = oftype(r.start + 1, r.start)
    next(r::UnitRange, state::T) = (oftype(T,state), state + 1)
    done(r::UnitRange, state::T) = i == oftype(i,r.stop) + 1
end

이것을 조각으로 분해해 봅시다. 먼저 함수 유형 구문이 있습니다. A --> BA 유형의 개체를 B 유형에 매핑하는 함수 유형입니다. 이 표기법의 튜플은 명백한 일을 합니다. 격리, 나는 것을 제안하고있어 f :: A --> B 선언 할 것이라고 f 일반적인 기능, 매핑 타입 A 입력 B . 이것이 의미하는 바는 약간 열린 질문입니다. A 유형의 인수에 적용할 때 f 유형의 결과가 B 합니까? fA 유형의 인수에만 적용할 수 있습니까? 자동 변환은 출력 시 입력 시 어디에서나 발생해야 합니까? 지금은 이 모든 작업이 메서드를 추가하지 않고 새로운 제네릭 함수를 생성하고 형식이 문서화용이라고 가정할 수 있습니다.

두 번째로 Iterable{T,S} 인터페이스의 선언이 있습니다. 이것은 Iterable 모듈과 약간 유사하게 만들고 추상 유형과 약간 유사하게 만듭니다. Iterable.start , Iterable.doneIterable.next 라는 일반 함수에 대한 바인딩이 있다는 점에서 모듈과 같습니다. IterableIterable{T}Iterable{T,S} 는 추상 유형이 가능한 모든 곳, 특히 메서드 디스패치에서 사용할 수 있는 유형과 같습니다.

셋째, UnitRangeIterable 인터페이스를 구현하는 방법을 정의하는 implement 블록이 있습니다. implement 블록 내부에서 Iterable.start , Iterable.doneIterable.next 기능을 사용할 수 있으며 마치 사용자가 import Iterable: start, done, next 수행한 것처럼 이러한 기능에 메소드 추가. 이 블록은 매개변수 유형 선언이 템플릿과 유사합니다. 블록 내부에서 UnitRange 는 우산 유형이 아니라 특정 UnitRange 의미합니다.

implement 블록의 주요 이점은 확장하려는 명시적으로 import 함수가 필요하지 않다는 것입니다. 이는 암시적으로 가져오기 때문에 사람들이 일반적으로 import 대해 혼동하기 때문에 좋습니다 import . 이것은 그것을 표현하는 훨씬 더 명확한 방법인 것 같습니다. Base 에서 사용자가 확장하려는 대부분의 일반 기능은 일부 인터페이스에 속해야 하므로 import 대한 대부분의 사용을 제거해야 한다고 생각합니다. 이름을 항상 정규화할 수 있으므로 이 이름을 완전히 없앨 수도 있습니다.

내가 떠돌던 또 다른 아이디어는 인터페이스 기능의 "내부" 버전과 "외부" 버전의 분리입니다. 이것이 의미하는 바는 "내부" 함수는 일부 인터페이스를 구현하기 위해 메서드를 제공하는 함수이고 "외부" 함수는 일부 인터페이스 측면에서 일반 기능을 구현하기 위해 호출하는 함수라는 것입니다. sort! 함수의 메소드를 볼 때 고려하십시오(사용되지 않는 메소드 제외):

julia> methods(sort!)
sort!(r::UnitRange{T<:Real}) at range.jl:498
sort!(v::AbstractArray{T,1},lo::Int64,hi::Int64,::InsertionSortAlg,o::Ordering) at sort.jl:242
sort!(v::AbstractArray{T,1},lo::Int64,hi::Int64,a::QuickSortAlg,o::Ordering) at sort.jl:259
sort!(v::AbstractArray{T,1},lo::Int64,hi::Int64,a::MergeSortAlg,o::Ordering) at sort.jl:289
sort!(v::AbstractArray{T,1},lo::Int64,hi::Int64,a::MergeSortAlg,o::Ordering,t) at sort.jl:289
sort!{T<:Union(Float64,Float32)}(v::AbstractArray{T<:Union(Float64,Float32),1},a::Algorithm,o::Union(ReverseOrdering{ForwardOrdering},ForwardOrdering)) at sort.jl:441
sort!{O<:Union(ReverseOrdering{ForwardOrdering},ForwardOrdering),T<:Union(Float64,Float32)}(v::Array{Int64,1},a::Algorithm,o::Perm{O<:Union(ReverseOrdering{ForwardOrdering},ForwardOrdering),Array{T<:Union(Float64,Float32),1}}) at sort.jl:442
sort!(v::AbstractArray{T,1},alg::Algorithm,order::Ordering) at sort.jl:329
sort!(v::AbstractArray{T,1}) at sort.jl:330
sort!{Tv<:Union(Complex{Float32},Complex{Float64},Float64,Float32)}(A::CholmodSparse{Tv<:Union(Complex{Float32},Complex{Float64},Float64,Float32),Int32}) at linalg/cholmod.jl:809
sort!{Tv<:Union(Complex{Float32},Complex{Float64},Float64,Float32)}(A::CholmodSparse{Tv<:Union(Complex{Float32},Complex{Float64},Float64,Float32),Int64}) at linalg/cholmod.jl:809

이러한 방법 중 일부는 공개적으로 사용하기 위한 것이지만 다른 방법은 공개 정렬 방법의 내부 구현의 일부일 뿐입니다. 실제로 이것이 있어야하는 유일한 공개 방법은 다음과 같습니다.

sort!(v::AbstractArray)

나머지는 소음이며 "내부"에 속합니다. 특히,

sort!(v::AbstractArray{T,1},lo::Int64,hi::Int64,::InsertionSortAlg,o::Ordering)
sort!(v::AbstractArray{T,1},lo::Int64,hi::Int64,a::QuickSortAlg,o::Ordering)
sort!(v::AbstractArray{T,1},lo::Int64,hi::Int64,a::MergeSortAlg,o::Ordering)

메소드의 종류는 정렬 알고리즘이 일반 정렬 기계에 연결하기 위해 구현하는 것입니다. 현재 Sort.Algorithm 는 추상 유형이고 InsertionSortAlg , QuickSortAlgMergeSortAlg 는 구체적인 하위 유형입니다. 인터페이스를 사용하면 Sort.Algorithm 대신 인터페이스가 될 수 있으며 특정 알고리즘이 이를 구현합니다. 이 같은:

# module Sort
interface Algorithm
    sort! :: (AbstractVector, Int, Int, Algorithm, Ordering) --> AbstractVector
end
implement InsertionSortAlg <: Algorithm
    function sort!(v::AbstractVector, lo::Int, hi::Int, ::InsertionSortAlg, o::Ordering)
        <strong i="17">@inbounds</strong> for i = lo+1:hi
            j = i
            x = v[i]
            while j > lo
                if lt(o, x, v[j-1])
                    v[j] = v[j-1]
                    j -= 1
                    continue
                end
                break
            end
            v[j] = x
        end
        return v
    end
end

우리가 원하는 분리는 다음을 정의하여 달성할 수 있습니다.

# module Sort
sort!(v::AbstractVector, alg::Algorithm, order::Ordering) =
    Algorithm.sort!(v,1,length(v),alg,order)

이것은 단지 sort! 대신 Algorithm.sort! 를 호출한다는 점을 제외하고 현재 우리가 하고 있는 것과 _매우_ 비슷합니다. 다양한 정렬 알고리즘을 구현할 때 "내부" 정의는 Algorithm.sort!sort! 함수가 아닙니다. 이것은 sort! 구현을 외부 인터페이스에서 분리하는 효과가 있습니다.

@StefanKarpinski 작성해 주셔서 감사합니다! 이것은 확실히 0.3 항목이 아닙니다. 이 시간에 이런 얘기를 꺼내서 죄송합니다. 0.3이 곧 또는 반년 후에 발생할지 확실하지 않습니다. ;-)

언뜻 보기에는 구현 섹션이 자체 코드 블록으로 정의되어 있다는 점이 정말 (!) 마음에 듭니다. 이를 통해 유형 정의에서 인터페이스를 직접 확인할 수 있습니다.

걱정하지 마세요. 릴리스를 안정화하는 동안 향후 기능에 대해 추측하는 데 아무런 해가 없습니다.

귀하의 접근 방식은 훨씬 더 근본적이며 일부 인터페이스 독립 문제도 해결하려고 합니다. 그것은 또한 언어를 조금 더 복잡하게 만드는 새로운 구조(즉, 인터페이스)를 언어에 가져옵니다(필연적으로 나쁜 것은 아닙니다).

나는 "인터페이스"를 추상 유형에 대한 주석으로 더 많이 봅니다. has 를 넣으면 인터페이스를 지정할 수 있지만 반드시 그럴 필요는 없습니다.

내가 말했듯이 인터페이스가 선언에서 직접 검증될 수 있다면 정말 좋겠습니다. 여기서 가장 덜 침습적인 접근 방식은 유형 선언 내에서 메서드를 정의하는 것입니다. 따라서 다음과 같은 예를 들면

type UnitRange{T} <: Iterable{T,T}
    start(r::UnitRange) = oftype(r.start + 1, r.start)
    next(r::UnitRange, state::T) = (oftype(T,state), state + 1)
    done(r::UnitRange, state::T) = i == oftype(i,r.stop) + 1
end

유형 선언 외부에서 함수를 정의하는 것은 여전히 ​​허용됩니다. 유일한 차이점은 내부 함수 선언이 인터페이스에 대해 검증된다는 것입니다.

그러나 다시 말하지만, 아마도 나의 "최소 침습적 접근"은 너무 근시안적일 것입니다. 잘 모르겠어.

이러한 정의를 유형 블록 내부에 넣는 것과 관련된 한 가지 문제는 이를 수행하려면 최소한 인터페이스의 다중 상속이 필요하고 서로 다른 인터페이스 간에 이름 충돌이 있을 수 있다는 점입니다. 확실하지 않지만 유형을 정의한 _후_ 어느 시점에서 유형이 인터페이스를 지원한다는 사실을 추가할 수도 있습니다.

@StefanKarpinski 당신이 이것에 대해 생각하고 있다는 것을 알게 되어

Graphs 패키지는 인터페이스 시스템이 가장 필요한 패키지입니다. 이 시스템이 http://graphsjl-docs.readthedocs.org/en/latest/interface.html에 설명된 인터페이스를 어떻게 표현할 수 있는지 보는 것은 흥미로울 것

@StefanKarpinski : 다중 상속 및 블록 내 함수 선언 문제를 완전히 이해하지 못했습니다. 유형 블록 내에서 상속된 모든 인터페이스를 확인해야 합니다.

그러나 인터페이스 구현을 "열기"를 원할 수도 있다는 것을 이해합니다. 그리고 in-type 함수 선언은 언어를 너무 복잡하게 만들 수 있습니다. #7025에서 구현한 접근 방식으로 충분할 수 있습니다. verify_interface 를 함수 선언 뒤에 넣거나(또는 단위 테스트에서) MethodError 연기합니다.

이 문제는 다른 인터페이스가 동일한 이름의 일반 기능을 가질 수 있다는 것입니다. 그러면 이름 충돌이 발생하고 명시적 가져오기를 수행하거나 정규화된 이름으로 메서드를 추가해야 합니다. 또한 어떤 메서드 정의가 어떤 인터페이스에 속하는지 명확하지 않게 만듭니다. 이것이 처음에 이름 충돌이 발생할 수 있는 이유입니다.

Btw, 언어에서 인터페이스를 또 다른 "사물"로 추가하는 것이 너무 비직교적이라는 데 동의합니다. 결국, 내가 제안서에서 언급했듯이, 그것들은 모듈과 약간 비슷하고 유형과 비슷합니다. 개념의 일부 통합이 가능할 것 같지만 방법이 명확하지 않습니다.

나는 몇 가지 이유로 언어 기능으로서의 인터페이스 모델보다 라이브러리로서의 인터페이스 모델을 선호합니다: 그것은 언어를 더 단순하게 유지하고(구체적인 반대가 아니라 선호하는 것이 인정됨) 기능이 선택 사항으로 남아 있고 쉽게 사용할 수 있음을 의미합니다. 실제 언어를 사용하지 않고 개선되거나 완전히 대체되었습니다.

특히, @tknopp 의 제안(또는 적어도 제안의 형태)이 낫다고 생각합니다. 언어에서 새로운 것을 요구하지 않고 정의 시간 검사를 제공합니다. 내가 볼 수 있는 주요 단점은 유형 변수를 처리할 수 있는 능력이 없다는 것입니다. 인터페이스 정의가 필수 기능 유형에 대해 유형 _predicates_를 제공하도록 하면 이것이 처리될 수 있다고 생각합니다.

내 제안에 대한 주요 동기 중 하나는 일반 기능에 메서드를 추가하기 위해 일반 기능을 _import_하지만 내보내지 않아야 하기 때문에 발생하는 많은 혼란입니다. 대부분의 경우 이것은 누군가가 비공식 인터페이스를 구현하려고 할 때 발생합니다. 그래서 이것이 일어나고 있는 것처럼 보이게 만듭니다.

메서드를 인터페이스에 속하는 것으로 완전히 제한하려는 경우가 아니라면 해결해야 할 직교 문제처럼 보입니다.

아니요, 확실히 좋은 제한처럼 보이지 않습니다.

@StefanKarpinski 는 인터페이스에서 파견할 수 있다고 언급했습니다. 또한 implement 구문에서 아이디어는 특정 유형이 인터페이스를 구현한다는 것입니다.

이것은 일반적으로 메소드가 특정 유형에 속하지 않고 유형의 튜플에 속하기 때문에 다중 디스패치와 약간 맞지 않는 것 같습니다. 따라서 메소드가 유형에 속하지 않는 경우 인터페이스(기본적으로 메소드 세트임)가 어떻게 유형에 속할 수 있습니까?

라이브러리 M을 사용하고 있다고 가정해 보겠습니다.

module M

abstract A
abstract B

type A2 <: A end
type A3 <: A end
type B2 <: B end

function f(a::A2, b::B2)
    # do stuff
end

function f(a::A3, b::B2)
    # do stuff
end

export f, A, B, A2, A3, B2
end # module M

이제 A와 B를 취하는 일반 함수를 작성하고 싶습니다.

using M

function userfunc(a::A, b::B, i::Int)
    res = f(a, b)
    res + i
end

이 예에서 f 함수는 AB 하는 임시 인터페이스를 형성하며 f 기능. 이 경우 인터페이스를 구현하기 위해 둘 중 어느 것을 고려해야 하는지 명확하지 않습니다.

AB 구체적인 하위 유형을 제공하려는 다른 모듈은 f 구현을 제공해야 합니다. 필수 메소드의 조합 폭발을 피하기 위해 라이브러리가 추상 유형에 대해 f 를 정의할 것으로 예상합니다.

module N

using M

type SpecialA <: A end
type SpecialB <: B end

function M.f(a::SpecialA, b::SpecialB)
    # do stuff
end

function M.f(a::A, b::SpecialB)
    # do stuff
end

function M.f(a::SpecialA, b::B)
    # do stuff
end

export SpecialA, SpecialB

end # module N

분명히 이 예제는 상당히 인위적으로 느껴지지만 (적어도 내 생각으로는) 다중 디스패치와 인터페이스를 구현하는 특정 유형의 개념 사이에 근본적인 불일치가 있는 것처럼 느껴진다는 것을 보여주기를 바랍니다.

import 혼란에 대한 귀하의 요점을 알 수 있습니다. 이 예제에서 using M 를 입력한 다음 f 에 메서드를 추가하려고 하면 예상한 대로 수행되지 않고 메서드를 추가해야 한다는 것을 기억하기 위해 몇 번 시도했습니다 M.f (또는 import 사용할 수 있음). 나는 인터페이스가 그 문제에 대한 해결책이라고 생각하지 않습니다. 방법을 더 직관적으로 추가하는 방법을 브레인스토밍하는 별도의 문제가 있습니까?

@abe-egnor 또한 보다 개방적인 접근 방식이 더 실현 가능하다고 생각합니다. 내 프로토타입 #7025에는 본질적으로 두 가지가 부족합니다.
a) 인터페이스를 정의하기 위한 더 나은 구문
b) 매개변수 유형 정의

나는 매개변수 유형 전문가가 아니기 때문에 b)는 더 깊은 경험을 가진 사람이 해결할 수 있다고 확신합니다.
관련) 매크로와 함께 갈 수 있습니다. 개인적으로 추상 유형 정의의 일부로 인터페이스를 직접 정의하기 위해 약간의 언어 지원을 사용할 수 있다고 생각합니다. has 접근 방식은 너무 근시안적일 수 있습니다. 코드 블록은 이것을 더 멋지게 만들 수 있습니다. 실제로 이것은 "내부" 인터페이스가 정의된 #4935와 매우 관련이 있으며 그녀는 공개 인터페이스에 관한 것입니다. 이 문제가 #4935보다 훨씬 더 중요하다고 생각하기 때문에 이것들을 묶을 필요는 없습니다. 그러나 여전히 구문상 현명한 사람은 두 사용 사례를 모두 고려하고 싶을 수 있습니다.

https://gist.github.com/abe-egnor/503661eb4cc0d66b4489 는 내가 생각했던 일종의 구현에 대한 첫 번째 찌르기를 했습니다. 간단히 말해서 인터페이스는 해당 인터페이스에 필요한 함수의 이름과 매개변수 유형을 정의하는 유형에서 사전까지의 함수입니다. @implement 매크로는 주어진 유형에 대한 함수를 호출한 다음 유형을 주어진 함수 정의에 연결하여 모든 함수가 정의되었는지 확인합니다.

좋은 점:

  • 인터페이스 정의 및 구현을 위한 간단한 구문입니다.
  • 다른 언어 기능과 직교하지만 잘 작동합니다.
  • 인터페이스 유형 계산은 임의로 화려할 수 있습니다(인터페이스 유형 매개변수에 대한 함수일 뿐입니다).

나쁜 점:

  • 매개변수를 인터페이스 유형으로 사용하려는 경우 매개변수화된 유형과 잘 작동하지 않습니다. 이것은 상당히 중요한 단점이지만 즉시 해결할 수 있는 방법이 없습니다.

매개변수화 문제에 대한 해결책이 있다고 생각합니다. 즉, 인터페이스 정의는 유형 값에 대한 함수가 아니라 유형 표현식에 대한 매크로여야 합니다. @implement 매크로는 유형 매개변수를 함수 정의로 확장하여 다음과 같은 것을 허용합니다.

<strong i="7">@interface</strong> stack(Container, Data) begin
  stack_push!(Container, Data)
end

<strong i="8">@implement</strong> stack{T}(Vector{T}, T) begin
  stack_push!(vec, x) = push!(vec, x)
end

이 경우, 유형 매개변수는 인터페이스에 정의된 메소드로 확장되므로 stack_push!{T}(vec::Vector{T}, x::T) = push!(vec, x) 확장됩니다. 이것이 정확히 옳은 것이라고 생각합니다.

시간이 되는 대로 초기 구현을 다시 작업하겠습니다. 아마 일주일 정도.

다른 프로그래밍 언어가 인터페이스, 상속 등에 대해 어떻게 하는지 알아보기 위해 인터넷을 조금 검색하고 몇 가지 아이디어를 생각해 냈습니다. (누군가가 여기에 관심이 있는 경우 내가 https://gist.github.com/mauro3/e3e18833daf49cdf8f60을 가져간 매우 거친 메모)

짧은 것은 인터페이스가 다음과 같이 구현될 수 있다는 것입니다.

  • 추상 유형에 대한 다중 상속 허용
  • 일반 함수를 추상 유형의 필드로 허용합니다.

이렇게 하면 추상 유형이 인터페이스로 바뀌고 구체적인 하위 유형은 해당 인터페이스를 구현하는 데 필요합니다.

긴 이야기:

내가 찾은 것은 "현대" 언어 중 일부가 하위 유형 다형성을 제거한다는 것입니다. 즉 유형을 직접 그룹화하지 않고 대신 인터페이스/특성/유형 클래스에 속하는 유형을 기반으로 유형을 그룹화합니다. 일부 언어에서는 인터페이스/특성/유형 클래스 간에 순서가 있고 서로 상속될 수 있습니다. 그들은 또한 그 선택에 대해 (대부분) 행복해 보입니다. 예: 이동
러스트 , 하스켈 .
Go는 세 가지 중 가장 덜 엄격하며 인터페이스를 암시적으로 지정할 수 있습니다. 즉, 유형이 인터페이스의 특정 기능 세트를 구현하는 경우 해당 인터페이스에 속합니다. Rust의 경우 인터페이스(특성)는 impl 블록에서 명시적으로 구현되어야 합니다. Go나 Rust 모두 다중 메소드를 가지고 있지 않습니다. Haskell에는 다중 메소드가 있으며 실제로 인터페이스(유형 클래스)에 직접 연결되어 있습니다.

어떤 의미에서 이것은 Julia가 하는 것과 유사합니다. 추상 유형은 (암시적) 인터페이스와 같습니다. 즉, 필드가 아니라 동작에 관한 것입니다. 이것은 @StefanKarpinski 도 위의 게시물 중 하나에서 관찰했으며 추가로 인터페이스를 갖는 것이 "너무 비직교적으로 느껴진다"고 말했습니다. 따라서 Julia에는 유형 계층(즉, 하위 유형 다형성)이 있는 반면 Go/Rust/Haskell에는 없습니다.

모든 유형을 None<: ... <:Any 계층 구조에 유지하면서 Julia의 추상 유형을 인터페이스/특성/유형 클래스로 바꾸는 것은 어떻습니까? 이것은 다음을 수반합니다:
1) (추상) 유형에 대한 다중 상속 허용(문제 #5)
2) 기능을 추상 유형과 연관시키는 것을 허용합니다(즉, 인터페이스 정의).
3) 추상(즉, 기본 구현) 및 구체적인 유형 모두에 대해 해당 인터페이스를 지정할 수 있습니다.

이것이 지금보다 더 세분화된 유형의 그래프로 이어질 수 있고 단계별로 구현될 수 있다고 생각합니다. 예를 들어 배열 유형은 다음과 같이 결합됩니다.

abstract Container  <: Iterable, Indexable, ...
end

abstract AbstractArray <: Container, Arithmetic, ...
    ...
end

abstract  Associative{K,V} <: Iterable, Indexable, Eq
    haskey :: (Associative, _) --> Bool
end

abstract Iterable{T,S}
    start :: Iterable --> S
    done  :: (Iterable,S) --> Bool
    next  :: (Iterable,S) --> (T,S)
end

abstract Indexable{A,I}
    getindex  :: (A,I) --> eltype(A)
    setindex! :: (A,I) --> A
    get! :: (A, I, eltype(A)) --> eltype(A)
    get :: (A, I, eltype(A)) --> eltype(A)
end

abstract Eq{A,B}
    == :: (A,B) --> Boolean
end
...

따라서 기본적으로 추상 유형은 필드로 일반 기능을 가질 수 있습니다(즉, 인터페이스가 됨). 반면 구체 유형에는 일반 필드만 있습니다. 이것은 예를 들어 사람들이 AbstractArray에서 파생되는 것과 반대로 컨테이너에 유용한 부분을 선택할 수 있기 때문에 AbstractArray에서 파생되는 너무 많은 문제를 해결할 수 있습니다.

이것이 좋은 생각이라면 해결해야 할 것이 많지만(특히 유형 및 유형 매개변수를 지정하는 방법) 생각해 볼 가치가 있습니까?

@ssfrr 은 인터페이스와 다중 디스패치가 호환되지

나는 또한 발견 동안 @StefanKarpinski 쓰기 업을 직접 사용하는 것을 읽는 abstract 대신 interface 감각을 만들 수 있습니다. 그러나 이 경우 abstractinterface 의 중요한 속성 하나를 상속하는 것이 중요합니다. implement interface _after_ 유형이 정의될 가능성 그런 다음 내 코드에서 typA가 algoB에 필요한 인터페이스를 구현한다고 선언하여 lib A의 유형 typA를 lib B의 알고리즘 algoB와 함께 사용할 수 있습니다(이는 구체적인 유형에 일종의 개방형 다중 상속이 있음을 의미한다고 생각합니다).

@mauro3 , 나는 실제로 당신의 제안을 정말 좋아합니다. 나에게 그것은 매우 "줄리안"하고 자연스럽게 느껴진다. 또한 인터페이스, 다중 상속 및 추상 유형 "필드"의 독특하고 강력한 통합이라고 생각합니다. abstract AlgorithmAlgorithm.sort! 를 선언하여 sort! 예제에 대한 그의 제안을 구현할 수 있기 때문에 이것이 "내부" 인터페이스 방법과 "외부" 인터페이스 방법을 구별하는 @StefanKarpinski 의 아이디어와 잘 Algorithm.sort! .

모두들 미안해

------------------ 原始邮件 ------------------
发件人: "Jacob Quinn" [email protected];
发送时间: 2014年9月12日(星期五) 上午6:23
收件人: "JuliaLang/julia" [email protected];
抄送: "구현" [email protected];
主题: Re: [julia] 추상 유형에 대한 인터페이스(#6975)

@mauro3 , 나는 실제로 당신의 제안을 정말 좋아합니다. 나에게 그것은 매우 "줄리안"하고 자연스럽게 느껴진다. 또한 인터페이스, 다중 상속 및 추상 유형 "필드"의 독특하고 강력한 통합이라고 생각합니다. 나는 또한 이것이 "내부" 대 "외부" 인터페이스 방법을 구별하는 @StefanKarpinski 의 아이디어와 잘


이 이메일에 직접 회신하거나 GitHub에서 확인하세요.

@implement 매우 죄송합니다. 우리가 당신에게 어떻게 핑을 댔는지 확실하지 않습니다. 아직 모르는 경우 화면 오른쪽에 있는 "구독 취소" 버튼을 사용하여 해당 알림에서 자신을 제거할 수 있습니다.

아니요, 그냥 사리라고 하기에는 너무 많은 도움이 될 수 없다는 말을 하고 싶어요

------------------ 原始邮件 ------------------
发件人: "pao" [email protected];
发送时间: 2014年9月13日(星期六) 晚上9:50
收件人: "JuliaLang/julia" [email protected];
抄送: "구현" [email protected];
主题: Re: [julia] 추상 유형에 대한 인터페이스(#6975)

@implement 매우 죄송합니다. 우리가 당신에게 어떻게 핑을 댔는지 확실하지 않습니다. 아직 모르는 경우 화면 오른쪽에 있는 "구독 취소" 버튼을 사용하여 해당 알림에서 자신을 제거할 수 있습니다.


이 이메일에 직접 회신하거나 GitHub에서 확인하세요.

우리는 당신을 기대하지 않습니다! 사용자 이름과 이름이 같은 Julia 매크로에 대해 이야기하고 있기 때문에 사고였습니다. 감사 해요!

저는 Rust에서 작업한 잠재적으로 흥미로운 기능(이 문제와 관련이 있을 수 있음)이 있음을 방금 보았습니다: http://blog.rust-lang.org/2014/09/15/Rust-1.0.html , 특히: https //github.com/rust-lang/rfcs/pull/195

THTT ("Tim Holy Trait Trick")를 본 후 지난 몇 주 동안 인터페이스/특성에 대해 더 많은 생각을 했습니다. 몇 가지 아이디어와 구현을 생각해 냈습니다 . 하나 또는 여러 유형을 포함하는 계약 으로 보아야 @StefanKarpinski 가 위에서 제안한 것처럼 dispatch에 특성을 사용할 수 있어야 합니다.

Nuff는 다음과 같이 내 패키지 Traits.jl을 사용하는 예를 말했습니다.

<strong i="12">@traitdef</strong> Eq{X,Y} begin
    # note that anything is part of Eq as ==(::Any,::Any) is defined
    ==(X,Y) -> Bool
end

<strong i="13">@traitdef</strong> Cmp{X,Y} <: Eq{X,Y} begin
    isless(X,Y) -> Bool
end

이것은 EqCmpXY 유형 간의 계약임을 선언합니다. Cmp 에는 Eq 가 supertrait로 있습니다. 즉, EqCmp 가 모두 충족되어야 합니다. @traitdef 본문에서 함수 서명은 정의해야 하는 메서드를 지정합니다. 반환 유형은 현재 아무 작업도 수행하지 않습니다. 유형은 특성을 명시적으로 구현할 필요가 없으며 기능을 구현하면 됩니다. 예를 들어 Cmp{Int,Float64} 가 실제로 특성인지 확인할 수 있습니다.

julia> istrait(Cmp{Int,Float64})
true

julia> istrait(Cmp{Int,String})
false

명시적 특성 구현은 아직 패키지에 없지만 추가하기가 상당히 간단해야 합니다.

_trait-dispatch_를 사용하는 함수는 다음과 같이 정의할 수 있습니다.

<strong i="31">@traitfn</strong> ft1{X,Y; Cmp{X,Y}}(x::X,y::Y) = x>y ? 5 : 6

이것은 해당 유형이 Cmp{X,Y} 를 충족해야 하는 제약 조건이 있는 두 개의 인수를 취하는 ft1 함수를 선언합니다. 다른 트레잇에 디스패치하는 다른 메소드를 추가할 수 있습니다.

<strong i="37">@traitdef</strong> MyT{X,Y} begin
    foobar(X,Y) -> Bool
end
# and implement it for a type:
type A
    a
end
foobar(a::A, b::A) = a.a==b.a

<strong i="38">@traitfn</strong> ft1{X,Y; MyT{X,Y}}(x::X,y::Y) = foobar(x,y) ? -99 : -999

이러한 특성 함수는 이제 일반 함수처럼 호출할 수 있습니다.

julia> ft1(4,5)
6

julia> ft1(A(5), A(6))
-999

나중에 트레이트에 다른 유형을 추가하는 것은 쉽습니다(ft1에 Unions를 사용하는 경우는 아님):

julia> ft1("asdf", 5)
ERROR: TraitException("No matching trait found for function ft1")
 in _trait_type_ft1 at

julia> foobar(a::String, b::Int) = length(a)==b  # adds {String, Int} to MyTr
foobar (generic function with 2 methods)

julia> ft1("asdf", 5)
-999

특성 기능의 _구현_ 및 해당 디스패치는 Tim의 트릭 과 단계적 기능을 기반으로 합니다(아래 참조). 특성 정의는 비교적 간단합니다. 수동으로 구현하려면 여기 를 참조

간단히 말해서, 특성 디스패치는

<strong i="51">@traitfn</strong> f{X,Y; Trait1{X,Y}}(x::X,y::Y) = x+y

이와 같은 것으로 (약간 단순화 됨)

f(x,y) = _f(x,y, checkfn(x,y))
_f{X,Y}(x::X,y::Y,::Type{Trait1{X,Y}}) = x+y
# default
checkfn{T,S}(x::T,y::S) = error("Function f not implemented for type ($T,$S)")
# add types-tuples to Trait1 by modifying the checkfn function:
checkfn(::Int, ::Int) = Trait1{Int,Int}
f(1,2) # 3

패키지에서 checkfn 의 생성은 stagedfunciton에 의해 자동화됩니다. 그러나 자세한 내용은 Traits.jl의 README를 참조하십시오.

_성능_ 간단한 특성 함수의 경우 생성된 기계 코드는 오리 유형의 코드와 동일합니다. 더 긴 기능의 경우 길이가 최대 20%까지 차이가 ​​있습니다. 왜 이것이 모두 인라인되어야한다고 생각했는지 잘 모르겠습니다.

( Traits.jl 사소한 변경 사항을 반영하기 위해 10월 27일 편집됨)

Traits.jl 패키지를 탐색할 준비가 되었습니까? 추가 정보는 " @traitimpl을 사용하여 인터페이스를 구현합니다(아직 완료되지 않음...)"라고 말합니다. 이것이 중요한 단점입니까?

탐색할 준비가 되었습니다(버그 포함 :-). 누락된 @traitimpl

<strong i="7">@traitimpl</strong> Cmp{T1, T2} begin
   isless(t1::T1, t2::T2) = t1.t < t2.f
end

함수를 수동으로 정의하기만 하면 됩니다.

Base.isless(t1::T1, t2::T2) = t1.t < t2.f

두 가지 유형 T1T2 .

@traitimpl 매크로를 추가했으므로 위의 예가 이제 작동합니다. 또한 사용법에 대한 세부 정보로 README를 업데이트했습니다. 그리고 @lindahua Graphs.jl 인터페이스의 일부를 구현하는 예제를 추가했습니다.
https://github.com/mauro3/Traits.jl/blob/master/examples/ex_graphs.jl

정말 멋지네요. 일반적으로 인터페이스가 개별 유형이 아니라 유형의 튜플 속성이라는 것을 인식하는 것이 특히 마음에 듭니다.

나는 또한 이것이 매우 멋지다고 생각합니다. 이 접근 방식에 대해 좋아할 것이 많습니다. 잘 했어.

:+1:

좋은 피드백 감사합니다! 코드를 약간 업데이트/리팩토링했으며 합리적으로 버그가 없고 놀기에 좋습니다.
이 시점에서 사람들이 사용 사례에 맞는지 확인하기 위해 스핀을 줄 수 있다면 아마도 좋을 것입니다.

이것은 새로운 관점에서 자신의 코드를 보게 하는 패키지 중 하나입니다. 아주 멋져요.

죄송합니다. 아직 진지하게 검토할 시간이 없었지만, 일단 검토하고 나면 몇 가지 항목을 리팩토링하고 싶을 것입니다...

패키지도 리팩토링하겠습니다 :)

나는 만일 특성이 사용 가능하다면(그리고 위의 제안처럼 다중 디스패치를 ​​허용한다면) 추상 유형 계층 메커니즘이나 추상 유형이 전혀 필요하지 않다고 생각합니다. 이것이 될 수 있습니까?

특성이 구현된 후 기본 및 나중에 전체 생태계의 모든 기능은 결국 특성만을 기반으로 하는 공개 API를 노출하고 추상 유형은 사라질 것입니다. 물론 추상 유형을 더 이상 사용하지 않음으로써 프로세스를 촉진할 수 있습니다.

이것을 조금 더 생각해보면 추상 유형을 특성으로 대체하려면 다음과 같은 유형을 매개변수화해야 합니다.

Array{X; Cmp{X}} # an array of comparables
myvar::Type{X; Cmp{X}} # just a variable which is comparable

나는 위의 mauro3 요점에 동의합니다. 특성(그의 정의에 따르면 매우 훌륭하다고 생각합니다)을 갖는 것은 다음과 같은 추상 유형과 동일합니다.

  • 다중 상속을 허용하고
  • 일반 함수를 필드로 허용

또한 특성이 정의 후에 유형에 할당되도록 하려면 "지연 상속"도 허용해야 합니다. 즉, 유형이 정의된 후 일부 추상 유형에서 상속된다는 것을 컴파일러에 알려야 합니다.

그래서 대체로 추상 유형 외부의 일부 특성/인터페이스 개념을 개발하면 동일한 것을 달성하기 위해 다른 방법을 도입하여 일부 복제가 유도될 것이라고 생각합니다. 이제 이러한 개념을 소개하는 가장 좋은 방법은 추상 유형에 기능을 천천히 추가하는 것이라고 생각합니다.

편집 : 물론 어느 시점에서 추상 유형에서 구체적인 유형을 상속하는 것은 더 이상 사용되지 않고 최종적으로 허용되지 않아야 합니다. 유형 특성은 암시적 또는 명시적으로 결정되지만 상속에 의해 결정되지는 않습니다.

추상 유형은 특성의 "지루한" 예가 아닙니까?

그렇다면 현재 구문을 유지하고 의미를 특성으로 간단히 변경할 수 있습니까(사용자가 원하는 경우 직교 자유 등 제공)?

_ Point{Float64} <: Pointy{Real} 예제도 해결할 수 있는지 궁금합니다(문제 번호가 있는지 확실하지 않음)?_

예, 당신이 옳다고 생각합니다. 현재 julia 추상 유형을 향상하여 특성 기능을 달성할 수 있습니다. 그들은 필요
1) 다중 상속
2) 함수 서명
3) "게으른 상속", 이미 정의된 유형에 새 특성을 명시적으로 제공

많은 작업처럼 보이지만 커뮤니티에 큰 손상 없이 천천히 성장할 수 있습니다. 그래서 적어도 우리는 그것을 얻었습니다 ;)

나는 우리가 무엇을 선택하든지 0.4에서 작업을 시작할 준비가 되지 않은 큰 변화가 될 것이라고 생각합니다. 추측해야 한다면 전통적인 다중 상속을 추가하는 방향보다 특성 방향으로 이동할 가능성이 더 높다고 장담합니다. 그러나 내 수정 구슬은 프리츠에 있으므로 시도하지 않고는 어떤 일이 일어날지 확신하기 어렵습니다.

FWIW, 아래 대화에서 유형 클래스에 대한 Simon Peyton-Jones의 토론은 하위 유형 대신 특성과 같은 것을 사용하는 방법에 대해 매우 유익한 정보를 제공합니다. http://research.microsoft.com/en-us/um/people/simonpj/ 논문/haskell-retrospective/ECOOP-July09.pdf

예, 벌레 통통!

@johnmyleswhite , 링크 주셔서 감사합니다. 매우 흥미롭습니다. 여기 에 그 비디오에 대한 링크가 있습니다. 부족한 부분을 채우기 위해 시청할 가치가 있습니다. 그 프레젠테이션은 우리가 여기서 받은 많은 질문에 영향을 미치는 것 같습니다. 그리고 흥미롭게도 유형 클래스의 구현은 Traits.jl(Tim의 트릭, 데이터 유형이 되는 특성)에 있는 것과 매우 유사합니다. Haskell의 https://www.haskell.org/haskellwiki/Multi-parameter_type_class 는 Traits.jl과 매우 유사합니다. 강연에서 그의 질문 중 하나는 "우리가 제네릭을 진심으로 채택한 후에도 여전히 서브타이핑이 필요한가?"입니다. (제네릭은 매개변수적 다형성 함수입니다. 제 생각에는 참조 ) @skariel@hayd 가 위에서 생각 같습니다 .

@skariel@hayd를 참조하면, Traits.jl에서같이 단일 매개변수 특성이 다른 계층, 즉 다중 상속을 가질 수 있다는 점을 제외하고는 실제로 추상 유형에 매우 가깝다고 생각합니다.

그러나 다중 매개 변수 특성은 약간 다른 것 같습니다. 적어도 제 생각에는 그랬습니다. 내가 그들을 보았 듯이, 추상적 인 유형의 형식 매개 변수는 대부분 다른 유형이 유형에 포함되는 것에 대해 것 같다 예를 들어, Associative{Int,String} DICT에 포함 말한다 IntString 값. Tr{Associative,Int,String}...Associative , Int s 및 Strings 사이에 "계약"이 있다고 말합니다. 하지만 Associative{Int,String} 도 그런 식으로 읽어야 합니다. 예를 들어 getindex(::Associative, ::Int) -> String , setindex!(::Associative, ::Int, ::String) ...

@mauro3 중요한 것은 Associative 유형의 객체를 함수에 대한 인수로 전달하여 Associative{Int,String} 자체를 생성할 수 있도록 하는 것입니다.

function f(A::Associative)
  a = A{Int,String}()  # create new associative
  a[1] = "one"
  return a
end

이것을 f(Dict) 와 같이 부를 수 있습니다.

@eschnett , 죄송합니다. 무슨 말씀이신지 이해가 되지 않습니다.

@mauro3 제가 너무 복잡하게 생각한 것 같아요; 무시해.

Traits.jl을 다음과 같이 업데이트했습니다.

  • 특성 모호성의 해결
  • 연결된 유형
  • 도움을 위해 @doc 사용
  • 특성 사양 방법의 더 나은 테스트

자세한 내용은 https://github.com/mauro3/Traits.jl/blob/master/NEWS.md 를 참조

@Rory-Finnegan은 인터페이스 패키지 https://github.com/Rory-Finnegan/Interfaces.jl을 구성했습니다.

나는 최근에 이것을 @mdcfrancis 와 논의했고 우리는 Clojure의 프로토콜과 유사한 것이 간단하고 실용적일 것이라고 생각합니다. 기본 기능은 (1) 프로토콜이 새로운 종류의 유형이고, (2) 일부 메서드 서명을 나열하여 프로토콜을 정의하고, (3) 다른 유형이 일치하는 메서드 정의를 사용하여 암시적으로 구현한다는 것입니다. 예를 들면

protocol Iterable
    start(::_)
    done(::_, state)
    next(::_, state)
end

그리고 isa(Iterable, Protocol)Protocol <: Type 있습니다. 당연히, 당신은 이것들에 파견할 수 있습니다. T <: Iterable 사용하여 유형이 프로토콜을 구현하는지 확인할 수 있습니다.

다음은 하위 유형 지정 규칙입니다.

P, Q를 프로토콜 유형이라고 하자
T를 비 프로토콜 유형이라고 하자

| 입력 | 결과 |
| --- | --- |
| P <: 모두 | 사실 |
| 하단 <: P | 사실 |
| (union,unionall,var) <: P | 정상적인 규칙을 사용하십시오. P를 기본 유형으로 취급 |
| P <: (union,unionall,var) | 일반 규칙을 사용 |
| 피 <: 피 | 사실 |
| 피 <: 문 | 검사 방법(Q) <: 방법(P) |
| 피 <: 티 | 거짓 |
| T <: P | P의 방법은 _ |

마지막 것은 큰 것입니다. T <: P를 테스트하려면 P의 정의에서 T를 _로 대체하고 각 서명에 대해 method_exists 를 확인합니다. 물론, 이것은 그 자체로 "이를 구현해야 합니다" 오류를 발생시키는 대체 정의가 매우 나쁜 것이 된다는 것을 의미합니다. 바라건대 이것은 미용상의 문제가 더 많습니다.

또 다른 문제는 예를 들어 start(::Iterable) 가 정의된 경우 이 정의가 순환적이라는 것입니다. 그러한 정의는 실제로 의미가 없습니다. 우리는 어떻게든 이것을 방지하거나 하위 유형 검사 중에 이 주기를 감지할 수 있습니다. 간단한 주기 감지가 문제를 해결한다고 100% 확신할 수는 없지만 그럴듯해 보입니다.

유형 교차의 경우 다음이 있습니다.

| 입력 | 결과 |
| --- | --- |
| P ∩ (union,unionall,tvar) | 일반 규칙을 사용 |
| 피 ∩ Q | 피 |
| 피 ∩ 티 | 티 |

P ∩ Q에 대한 몇 가지 옵션이 있습니다.

  1. P 또는 Q를 반환하여 과도하게 근사합니다(예: 사전순으로 먼저). 이것은 유형 추론과 관련하여 건전하지만 다른 곳에서는 성가실 수 있습니다.
  2. P와 Q의 서명 조합을 포함하는 새로운 임시 프로토콜을 반환합니다.
  3. 교차로 유형. 아마도 프로토콜로만 제한됩니다.

P ∩ T는 까다롭습니다. 비프로토콜 유형은 유형 계층의 한 영역으로 제한한다는 점에서 프로토콜 유형보다 "작기" 때문에 T는 좋은 보수적 근사치입니다. ). 이것보다 더 잘하려면 하위 유형 알고리즘을 정밀 검사해야 하고 웜 캔 이후에 웜 캔을 열기 때문에 초기 구현에서 피하고 싶은 일반적인 교차 유형이 필요한 것 같습니다.

특이성: P는 P<:Q인 경우에만 Q보다 더 특이적입니다. 그러나 P ∩ Q는 항상 비어 있지 않기 때문에 동일한 슬롯에 다른 프로토콜을 사용하는 정의가 모호한 경우가 많습니다. 이는 원하는 것처럼 보입니다(예: "x가 Iterable이면 이 작업을 수행하지만 x가 Printable이면 다음을 수행합니다. 저것").
그러나 필요한 명확한 정의를 표현할 수 있는 편리한 방법이 없으므로 이것은 아마도 오류일 것입니다.

#13412 이후에 프로토콜은 튜플 유형의 조합을 통해 UnionAll _ 로 "인코딩"될 수 있습니다(각 내부 튜플의 첫 번째 요소는 해당 함수의 유형임). 이것은 이전에 나에게 없었던 그 디자인의 이점입니다. 예를 들어 프로토콜의 구조적 하위 유형은 자동으로 떨어지는 것처럼 보입니다.

물론 이러한 프로토콜은 "단일 매개변수" 스타일입니다. 나는 이것의 단순함을 좋아하고 또한 T <: Iterable 처럼 우아하게 유형 그룹을 처리하는 방법을 모르겠습니다.

과거 x-ref https://github.com/JuliaLang/julia/issues/5#issuecomment -37995516에서 이 아이디어에 대한 의견이 있었습니다.

예를 들어

protocol Iterable{T}
    start(::_)::T
    done(::_, state::T)
    next(::_, state::T)
end

와, 나는 이것을 정말로 좋아합니다 (특히 @Keno 의 확장으로)!

+1 이것이 바로 내가 원하는 것입니다!

@Keno 확실히 이 기능에 대한 좋은 업그레이드 경로이지만 연기할 이유가 있습니다. 반환 유형과 관련된 모든 것은 물론 매우 문제가 있습니다. 매개변수 자체는 개념적으로 훌륭하고 훌륭하지만 구현하기가 약간 어렵습니다. 모든 메소드의 존재를 확인하는 프로세스 주변의 유형 환경을 유지 관리해야 합니다.

배열과 같은 유형에 대한 O(1) 선형 인덱싱과 같은 특성을 이 체계에 집어넣을 수 있는 것 같습니다. hassomeproperty(::T) = true (그러나 hassomeproperty(::Any) = false _not_ )와 같은 더미 메서드를 정의한 다음

protocol MyProperty
hassomeproperty(::_)
end

_ 가 다음과 같이 프로토콜 정의에서 동일한 방법으로 여러 번 나타날 수 있습니까?

protocol Comparable
  >(::_, ::_)
  =(::_, ::_0
end

_ 프로토콜 정의에서 동일한 방법으로 여러 번 나타날 수 있습니다.

예. _ 의 모든 인스턴스에 대해 후보 유형을 삭제하기만 하면 됩니다.

@JeffBezanson 정말 기대됩니다. 저에게 특히 주목할만한 것은 프로토콜의 '원격성'입니다. 프로토콜의 존재에 대한 지식이 없는 유형의 작성자 없이 유형에 대한 특정/사용자 정의 프로토콜을 구현할 수 있다는 점에서.

메서드를 언제든지 동적으로 정의할 수 있다는 사실은 어떻습니까(예: @eval )? 그런 다음 유형이 주어진 프로토콜의 하위 유형인지 여부는 일반적으로 정적으로 알 수 없으며 많은 경우에 동적 디스패치를 ​​피하는 최적화를 무효화하는 것처럼 보입니다.

예, 이것은 #265를 더 나쁘게 만듭니다. 메서드가 추가될 때 디스패치 및 생성된 코드가 변경되어야 하는 동일한 문제입니다. 단지 더 많은 종속성 에지가 있는 것뿐입니다.

이렇게 발전하는 모습이 보기 좋습니다! 물론, 나는 다중 매개변수 특성이 앞으로 나아갈 길이라고 주장하고 싶습니다. 그러나 특성의 95%는 어쨌든 단일 매개변수일 것입니다. 그것은 그들이 여러 파견과 너무 잘 어울린다는 것입니다! 필요한 경우 나중에 다시 검토할 수 있습니다. 충분했다.

몇 가지 의견:

@Keno 의 제안(그리고 실제로 Jeff의 원본에서는 state )을 연관 유형이라고 합니다. 반환 유형이 없어도 유용합니다. Rust에는 적절한 수동 항목이 있습니다. Rust에서만큼 필요하지는 않지만 좋은 아이디어라고 생각합니다. 나는 그것이 특성의 매개변수가 되어야 한다고 생각하지 않습니다: Iterable 에 디스패치하는 함수를 정의할 때 T 가 무엇인지 알지 못할 것입니다.

내 경험상 method_exists 는 현재 형식으로 사용할 수 없습니다(#8959). 그러나 아마도 이것은 #8974(또는 이것으로)에서 수정될 것입니다. Traits.jl을 수행할 때 특히 매개변수화된 및 vararg 함수를 설명하기 위해 특성-시그니처에 대해 메서드 서명을 일치시키는 것이 가장 어려운 부분임을 발견했습니다( 참조 ).

상속도 가능하지 않을까?

기본 구현을 정의할 수 있는 메커니즘을 보고 싶습니다. 고전적인 것은 비교를 위해 = , < , > , <= , >= 중 두 개만 정의하면 된다는 것입니다. 아마도 이것이 Jeff가 언급한 주기가 실제로 유용한 곳일 것입니다. 위의 예를 계속해서 start(::Indexable) = 1done(i::Indexable,state)=length(i)==state 하면 기본값이 됩니다. 따라서 많은 유형은 next 만 정의하면 됩니다.

좋은 점. 연관된 유형이 Iterable{T} 의 매개변수와 다소 다르다고 생각합니다. 내 인코딩에서 매개변수는 "Foo 유형이 이 프로토콜을 구현하는 T가 존재합니까?" 내부의 모든 것에 대해 실존적으로 수량화됩니다.

예, 쉽게 protocol Foo <: Bar, Baz 허용하고 Bar와 Baz의 서명을 Foo로 복사하면 됩니다.

다중 매개변수 특성은 확실히 강력합니다. 서브타이핑과 통합하는 방법에 대해 생각하는 것은 매우 흥미로운 일이라고 생각합니다. TypePair{A,B} <: Trait 와 같은 것을 가질 수 있지만 그것은 옳지 않은 것 같습니다.

귀하의 제안(기능 측면에서)은 실제로 Clojure보다 Swift에 더 가깝습니다.

명목형(유형)과 구조적(프로토콜) 하위 유형을 혼합하는 것이 이상해 보입니다(그리고 미래에 혼란의 원인이 될 것 같습니다).

나는 또한 수학/행렬 연산을 위한 프로토콜의 표현력에 대해 약간 회의적입니다. 인터페이스가 명확하게 지정된 Iteration보다 복잡한 예제(행렬 연산)를 통해 생각하는 것이 더 계몽적이라고 생각합니다. 예를 들어 core.matrix 라이브러리를 참조하십시오.

나는 동의한다; 이 시점에서 우리는 프로토콜의 예를 수집하고 우리가 원하는 대로 작동하는지 확인해야 합니다.

당신이 이것을 상상하는 방식으로, 프로토콜은 그들의 메소드가 속한 네임스페이스가 될까요? 즉 쓸 때

protocol Iterable
    start(::_)
    done(::_, state)
    next(::_, state)
end

이것이 제네릭 함수 start , donenext 를 정의하고 정규화된 이름이 Iterable.start , Iterable.done 가 되는 것이 자연스러워 보입니다 next Iterable.doneIterable.next . 유형은 Iterable 구현하지만 Iterable 프로토콜에서 모든 일반 기능을 구현합니다. 나는 얼마 전에 이것과 매우 유사한 것을 제안했지만(지금은 찾을 수 없음), 다른 한편으로는 프로토콜을 구현하고 싶을 때 다음과 같이 하십시오.

implement T <: Iterable
    # in here `start`, `done` and `next` are automatically imported
    start(x::T) = something
    done(x::T, state) = whatever
    next(x::T, state) = etcetera, nextstate
end

이것은 @mdcfrancis가 언급한 "원격성"에 implement 블록이 사용하는 거의 모든 필요를 제거하는 것이다 import 대신 using 큰 도움이 될 것이다.

얼마 전에 이것과 매우 유사한 것을 제안했습니다(지금은 찾을 수 없음)

아마도 https://github.com/JuliaLang/julia/issues/6975#issuecomment -44502467 및 이전 https://github.com/quinnj/Datetime.jl/issues/27#issuecomment -31305128? (편집: https://github.com/JuliaLang/julia/issues/6190#issuecomment-37932021도 있습니다.)

네, 바로 그것입니다.

@StefanKarpinski 빠른 코멘트,

  • 현재 iterable을 구현하는 모든 클래스는 우리가 제안한 대로 프로토콜을 명시적으로 구현하도록 수정되어야 합니다. 현재 제안은 기본에 정의를 추가하기만 하면 기존의 모든 클래스를 프로토콜로 '리프트'합니다.
  • 반복 가능한 정의에 추가 기능을 추가하는 MyModule.MySuperIterable을 정의하면 하나의 추가 메서드를 추가하는 대신 각 클래스에 대해 많은 상용구 코드를 작성해야 합니다.
  • 나는 당신이 제안한 것이 원격성을 상쇄한다고 생각하지 않습니다. 그것은 단지 동일한 목표를 달성하기 위해 많은 추가 코드를 작성해야 한다는 것을 의미합니다.

프로토콜에 대한 일종의 상속이 허용된 경우 MySuperIterabe,
기존 메서드를 재사용하기 위해 Base.Iterable을 확장할 수 있습니다.

문제는 당신이 단지 방법의 선택을 원하는 경우일 것입니다.
프로토콜이지만 이는 원래 프로토콜이
처음부터 복합 프로토콜이어야 합니다.

@mdcfrancis – 첫 번째 요점은 좋은 것입니다. 비록 제가 제안하는 것은 기존 코드를 손상시키지 않을 것이지만, 이는 사람들의 코드가 디스패치에 의존하기 전에 해당 유형에 대한 프로토콜에 "옵트인"해야 한다는 것을 의미합니다. 일하고있는.

MyModule.MySuperIterable 지점을 확장할 수 있습니까? 나는 여분의 장황함이 어디에서 오는지 알지 못합니다. 예를 들어 다음과 같은 것이 있을 수 있습니다.

protocol Enumerable <: Iterable
    # inherits start, next and done; adds the following:
    length(::_) # => Integer
end

본질적으로 @ivarne이 말한 것입니다.

위의 특정 디자인에서 프로토콜은 네임스페이스가 아니라 다른 유형 및 기능에 대한 설명일 뿐입니다. 그러나 이것은 아마도 내가 핵심 유형 시스템에 집중하고 있기 때문일 것입니다. 모듈과 프로토콜의 조합으로 확장되는 구문 설탕을 상상할 수 있습니다.

module Iterable

function start end
function done end
function next end

jeff_protocol the_protocol
    start(::_)
    done(::_, state)
    next(::_, state)
end

end

그런 다음 Iterable이 유형으로 처리되는 컨텍스트에서 Iterable.the_protocol 합니다.

jeff/mdcfrancis 프로토콜이 여기의 다른 모든 것과 매우 직교한다고 느끼기 때문에 저는 이 관점을 좋아합니다. 나에게 "줄리안"을 느끼고 싶지 않다면 "X는 프로토콜 Y를 구현한다"라고 말할 필요가 없는 가벼운 느낌.

이 문제를 왜 구독했는지, 언제 구독했는지 모르겠습니다. 그러나 이 프로토콜 제안으로 내가 여기서 제기한 질문을 해결할 수

기술적으로 추가할 사항은 없지만 Julia(일종의)에서 야생에서 사용되는 "프로토콜"의 예는 JuMP가 솔버의 기능을 결정하는 것입니다. 예:

https://github.com/JuliaOpt/JuMP.jl/blob/master/src/solvers.jl#L223 -L246

        # If we already have an MPB model for the solver...
        if m.internalModelLoaded
            # ... and if the solver supports updating bounds/objective
            if applicable(MathProgBase.setvarLB!, m.internalModel, m.colLower) &&
               applicable(MathProgBase.setvarUB!, m.internalModel, m.colUpper) &&
               applicable(MathProgBase.setconstrLB!, m.internalModel, rowlb) &&
               applicable(MathProgBase.setconstrUB!, m.internalModel, rowub) &&
               applicable(MathProgBase.setobj!, m.internalModel, f) &&
               applicable(MathProgBase.setsense!, m.internalModel, m.objSense)
                MathProgBase.setvarLB!(m.internalModel, copy(m.colLower))
                MathProgBase.setvarUB!(m.internalModel, copy(m.colUpper))
                MathProgBase.setconstrLB!(m.internalModel, rowlb)
                MathProgBase.setconstrUB!(m.internalModel, rowub)
                MathProgBase.setobj!(m.internalModel, f)
                MathProgBase.setsense!(m.internalModel, m.objSense)
            else
                # The solver doesn't support changing bounds/objective
                # We need to build the model from scratch
                if !suppress_warnings
                    Base.warn_once("Solver does not appear to support hot-starts. Model will be built from scratch.")
                end
                m.internalModelLoaded = false
            end
        end

멋지네요. 유용합니다. m.internalModel 가 프로토콜을 구현하는 것으로 충분합니까, 아니면 두 인수가 모두 중요합니까?

예, m.internalModel 이 프로토콜을 구현하는 것으로 충분합니다. 다른 인수는 대부분 그냥 벡터입니다.

예, m.internalModel 이 프로토콜을 구현하기에 충분합니다.

야생에서 프로토콜의 예를 찾는 좋은 방법은 아마도 applicablemethod_exists 호출을 검색하는 것입니다.

Elixir 도 프로토콜을 구현하는 것으로 보이지만 표준 라이브러리의 프로토콜 수(정의를 벗어나는)는 상당히 제한되어 있습니다.

프로토콜과 추상 유형 사이의 관계는 무엇입니까? 원래 문제 설명은 추상 유형에 프로토콜을 첨부하는 것과 같은 것을 제안했습니다. 실제로, (지금은 비공식적인) 대부분의 프로토콜이 현재 추상 유형으로 구현되어 있는 것 같습니다. 프로토콜 지원이 추가될 때 추상 유형은 무엇에 사용됩니까? API를 선언할 방법이 없는 유형 계층 구조는 그다지 유용하게 들리지 않습니다.

아주 좋은 질문입니다. 거기에는 많은 옵션이 있습니다. 첫째, 추상 유형과 프로토콜이 개체를 그룹화하는 두 가지 방법이기는 하지만 매우 직교적이라는 점을 지적하는 것이 중요합니다. 추상 유형은 순전히 명목입니다. 개체에 집합에 속하는 것으로 태그를 지정합니다. 프로토콜은 순전히 구조적입니다. 개체에 특정 속성이 있는 경우 해당 개체는 집합에 속합니다. 따라서 일부 옵션은

  1. 둘 다 가지고 있으면 됩니다.
  2. 프로토콜을 추상 유형과 연관시킬 수 있어야 합니다. 예를 들어 유형이 자체적으로 하위 유형을 선언할 때 프로토콜 준수 여부를 확인합니다.
  3. 추상 유형을 완전히 제거하십시오.

(2)와 같은 것이 있다면 그것이 실제로 단일 기능이 아니라 명목형 및 구조적 유형의 조합이라는 것을 인식하는 것이 중요하다고 생각합니다.

추상 유형이 유용해 보이는 한 가지는 매개변수(예 convert(AbstractArray{Int}, x) 입니다. AbstractArray 가 프로토콜인 경우 요소 유형 Int 은 프로토콜 정의에서 반드시 언급할 필요는 없습니다. 메소드가 필요한 _aside_ 유형에 대한 추가 정보입니다. 따라서 AbstractArray{T}AbstractArray{S} 는 동일한 방법을 지정하더라도 여전히 다른 유형이므로 명목 유형을 다시 도입했습니다. 따라서 이러한 유형 매개변수의 사용은 일종의 명목 유형을 요구하는 것 같습니다.

그렇다면 2. 우리에게 다중 추상 상속을 줄까요?

그렇다면 2. 우리에게 다중 추상 상속을 줄까요?

아니요. 기능을 통합하거나 결합하는 방법이지만 각 기능은 여전히 ​​현재 가지고 있는 속성을 가집니다.

다중 추상 상속을 허용하는 것은 거의 직교에 가까운 또 다른 디자인 결정이라는 점을 추가해야 합니다. 어쨌든 추상 명목 유형을 너무 많이 사용하는 경우의 문제는 (1) 프로토콜의 사후 구현을 잃을 수 있다는 것입니다(A 사람이 유형을 정의하고 B 사람이 A에 대한 프로토콜 및 구현을 정의함). 프로토콜의 구조적 하위 유형을 잃을 수 있습니다.

현재 시스템의 유형 매개변수가 어떻게든 암시적 인터페이스의 일부가 아닙니까? 예를 들어 이 정의는 ndims{T,n}(::AbstractArray{T,n}) = n 에 의존하며 많은 사용자 정의 함수도 마찬가지입니다.

따라서 새로운 프로토콜 + 추상 상속 시스템에는 AbstractArray{T,N}ProtoAbstractArray 있습니다. 이제 명목상 AbstractArray 이 아닌 유형은 TN 매개변수가 무엇인지 지정할 수 있어야 합니다. 아마도 eltype 하드 코딩을 통해 및 ndims . 그런 다음 AbstractArray 의 모든 매개변수화된 함수는 매개변수 대신 eltypendims 를 사용하도록 다시 작성해야 합니다. 따라서 프로토콜이 매개변수도 전달하도록 하는 것이 더 합리적일 수 있으므로 연결된 유형은 결국 매우 유용할 수 있습니다. (구체적인 유형에는 여전히 매개변수가 필요합니다.)

또한 @malmaud 의 트릭을 사용하여 유형을 프로토콜로 그룹화합니다. https://github.com/JuliaLang/julia/issues/6975#issuecomment -161056795는 명목 유형 지정과 유사합니다. 그룹화는 전적으로 유형 선택 및 유형은 (사용 가능한) 인터페이스를 공유하지 않습니다. 그렇다면 추상 유형과 프로토콜이 꽤 많이 겹칠까요?

예, 추상 유형의 매개변수는 확실히 일종의 인터페이스이며 eltypendims 와 어느 정도 중복됩니다. 주요 차이점은 추가 메서드 호출 없이 직접 디스패치할 수 있다는 것입니다. 관련 유형을 사용하면 추상 유형을 프로토콜/특성으로 대체하는 데 훨씬 더 가깝다는 데 동의합니다. 구문은 어떻게 생겼습니까? 이상적으로는 하위 유형 지정과 메서드 호출 간에 순환 종속성이 없기 때문에 메서드 호출보다 약합니다.

나머지 질문은 관련된 추상 유형의 일부가 되지 않고 _없는_ 프로토콜을 구현하는 것이 유용한지 여부입니다. 반복 및 인덱싱이 가능하지만 종종 컨테이너 대신 "스칼라" 수량으로 처리되는 문자열을 예로 들 수 있습니다. 이것이 얼마나 자주 발생하는지 모르겠습니다.

귀하의 "메서드 호출" 진술을 잘 이해하지 못하는 것 같습니다. 따라서 구문에 대한 이 제안은 귀하가 요청한 것과 다를 수 있습니다.

protocol PAbstractArray{T,N}
    size(_)
    getindex(_, i::Int)
    ...
end

type MyType1
    a::Array{Int,1}
    ...
end

impl MyType for PAbstractArray{Int,1}
    size(_) = size(_.a)
    getindex(_, i::Int) = getindex(_.a,i)
    ...
end

# an implicit definition could look like:
associatedT(::Type{PAbstractArray}, :T, ::Type{MyType}) = Int
associatedT(::Type{PAbstractArray}, :N, ::Type{MyType}) = 1
size(mt::MyType) = size(mt.a)
getindex(mt::MyType, i::Int) = getindex(mt.a,i)


# parameterized type
type MyType2{TT, N, T}
    a::Array{T, N}
    ...
end

impl MyType2{TT,N,T} for PAbstractArray{T,N}
    size(_) = size(_.a)
    getindex(_, i::Int) = getindex(_.a,i)
    ...
end

프로토콜 유형의 하위 유형이 정의되는 방식에 따라 작동할 수 있습니다. 예를 들어, 주어진

protocol PAbstractArray{eltype,ndims}
    size(_)
    getindex(_, i::Int)
    ...
end

protocol Indexable{eltype}
    getindex(_, i::Int)
end

PAbstractArray{Int,1} <: Indexable{Int} 있습니까? 매개변수가 이름으로 일치하면 이것이 매우 잘 작동할 수 있다고 생각합니다. eltype(x)x 유형의 eltype 매개변수를 반환하도록 하는 정의를 자동화할 수도 있습니다.

저는 특히 impl 블록 안에 메서드 정의를 넣는 것을 좋아하지 않습니다. 대부분 단일 메서드 정의가 여러 프로토콜에 속할 수 있기 때문입니다.

따라서 이러한 메커니즘을 사용하면 더 이상 추상 유형이 필요하지 않은 것처럼 보입니다. AbstractArray{T,N} 는 프로토콜이 될 수 있습니다. 그런 다음 (프로토콜의) 다중 상속을 자동으로 얻습니다. 또한 프로토콜 상속만 지원되기 때문에 구체적인 유형에서 상속하는 것이 불가능합니다(이는 우리가 때때로 새로 온 사람들에게서 듣는 불만입니다).

제쳐두고: Callable 특성을 표현할 수 있다면 정말 좋을 것입니다. 다음과 같이 보여야 합니다.

protocol Callable
    ::TupleCons{_, Bottom}
end

여기서 TupleCons 는 튜플의 첫 번째 요소와 나머지 요소와 개별적으로 일치합니다. 아이디어는 _ 대한 메소드 테이블이 비어 있지 않은 한 일치한다는 것입니다(하단은 모든 인수 튜플 유형의 하위 유형임). 실제로 TupleCons{a, TupleCons{b, EmptyTuple}} 대한 Tuple{a,b} 구문을 만들고 싶을 수도 있습니다(#11242 참조).

나는 그것이 사실이라고 생각하지 않습니다. 모든 유형 매개변수는 _제약조건을 사용하여_ 실존적으로 수량화되어 있으므로 추상 유형과 프로토콜은 직접 대체할 수 없습니다.

@jakebolewski 예를 생각해

어쩌면 내가 요점을 놓치고 있을지 모르지만 프로토콜은 다음과 같은 제약 조건이 있는 적당히 복잡한 추상 유형을 어떻게 인코딩할 수 있습니까?

typealias BigMatrix ∃T, T <: Union{BigInt,BigFloat} AbstractArray{T,2}

명목상 모든 가능성을 열거할 필요 없이?

제안된 Protocol 제안은 내가 강조하려고 했던 추상 하위 유형에 비해 엄격하게 덜 표현적입니다.

다음을 상상할 수 있습니다(당연히 디자인을 실제 한계까지 확장).

BigMatrix = ∃T, T<:Union{BigInt, BigFloat} protocol { eltype = T, ndims = 2 }

기존 추상 유형의 표현력과 일치하도록 연결된 유형 또는 명명된 유형 속성과 같은 것이 필요하다는 관찰과 함께 진행됩니다. 이를 통해 잠재적으로 거의 호환성을 가질 수 있습니다.

AbstractArray = ∃T ∃N protocol { eltype=T, ndims=N }

객체의 데이터 필드에 대한 구조적 하위 유형 지정은 나에게 그다지 유용해 보이지 않았지만 _types_ 속성에 적용하는 대신 많은 의미가 있는 것 같습니다.

나는 또한 이것이 모호성 문제에서 탈출구를 제공할 수 있다는 것을 깨달았습니다. 두 유형의 교차점은 일부 매개변수에 대해 충돌하는 값이 있는 경우 비어 있습니다. 따라서 명확한 Number 유형을 원하면

protocol Number
    super = Number
    +(_, _)
    ...
end

이것은 super 를 또 다른 유형 속성으로 보고 있습니다.

제안된 프로토콜 구문이 마음에 들지만 몇 가지 메모가 있습니다.

하지만 그러면 내가 모든 것을 오해할 수 있습니다. 나는 최근에야 Julia에 대해 작업하고 싶은 분야로 본격적으로 조사하기 시작했고 아직 유형 시스템에 대해 완벽하게 이해하지 못합니다.

(a) 위에서 작업한 @mauro3 의 특성 기능이 더 흥미로울 것

protocol Foo{bar}
    ...
end

protocol Bar{foo<:Foo}
   ...
end

그리고 그것은 또한 Foo 프로토콜이 동일한 정의에서 Bar 프로토콜을 참조하는 것을 허용하지 않는 주요 문제를 노출합니다.

(비)

PAbstractArray{Int,1} <: Indexable{Int} 가 있습니까? 매개변수가 이름으로 일치하면 이것이 매우 잘 작동할 수 있다고 생각합니다.

나는 우리가 _NAME_에 의해 매개 변수와 일치 할 확실한 이유 (I가 될 것을 데려 갈거야 아니에요 eltype 나는이 부분을 무시하시기 바랍니다 오해하는 경우, 이름). 잠재적인 기능 시그니처만 일치시키면 안 됩니다. 명명을 사용하는 주요 문제는 다음을 방지하기 때문입니다.

module SomeBigLibrary
  # Assuming required definitions

  protocol Baz{el1type}
    Base.foo(_, i::el1type) # say `convert`
    baz(_)
  end
end

module SomeOtherLibrary
  # Assuming required definitions

  protocol Bar{el2type}
    Base.foo(_, i::el2type)
    bar(_)
  end
end

module My
  # Assuming required definitions

  protocol Protocol{el_type} # What do I put here to get both subtypes correctly!
    Base.foo(_, i::el_type)
    SomeBigLibrary.baz(_)
    SomeOtherLibrary.bar(_)
  end
end

다른 한편으로는 프로토콜이 우리가 원하는 특정 유형 계층만 노출하도록 합니다. match Iterable 이름을 지정하지 않으면 iterable 구현의 이점을 얻을 수 없습니다(또한 종속성에서 가장자리를 그리지 않음). 그러나 다음을 수행할 수 있는 기능 외에 사용자가 무엇을 _게득_하는지 잘 모르겠습니다...

(c) 그래서, 나는 뭔가를 놓치고 있을지도 모르지만 명명된 유형이 유용한 주요 목적은 상위 집합의 다른 부분이 어떻게 작동하는지 설명하는 것이 아닙니까? Number 계층 구조와 추상 유형 SignedUnsigned 하면 둘 다 Integer 프로토콜을 구현하지만 때때로 매우 다르게 동작합니다. 그들 사이에 구별하기 위해 우리는 지금 특별한 정의하도록 강요 negate 만에 Signed (우리가 실제로 부정 할 수 반환 형식없이 특히 어려운 유형을 Unsigned 타입)?

이것이 super = Number 예제에서 설명한 문제라고 생각합니다. bitstype Int16 <: Signed 선언할 때(내 다른 질문은 Number 또는 Signed 가 형식 속성이 있는 프로토콜로 구체적인 형식에 적용되는 방법입니다) Unsigned 프로토콜로 표시된 유형과 다른 것으로 표시하는 Signed ( super = Signed ) 프로토콜? 명명된 형식 매개 변수가 이상하다고 생각하기 때문이 아니라 내 보기에 이상한 솔루션이기 때문입니다. 두 프로토콜이 super에 배치된 유형을 제외하고 정확히 일치한다면 어떻게 다른가요? 그리고 더 큰 유형(프로토콜)의 하위 집합 간의 동작에 차이가 있다면 실제로 추상 유형의 목적을 재발명하는 것이 아닌가요?

(d) 문제는 추상 유형이 동작을 구별하기를 원하고 프로토콜이 동작이 표현되는 특정 기능(종종 다른 동작과 관계없이)을 보장하기를 원한다는 것입니다. 그러나 우리는 프로토콜이 보장하고 동작 추상 유형 분할을 허용하는 기능을 완성하려고 노력하고 있습니다.

우리가 자주 건너뛰는 솔루션은 구현에서 문제가 있는 "유형이 추상 클래스를 구현하려는 의도를 선언하고 준수 여부를 확인하도록 함"(순환 참조, 유형 블록 또는 impl 내부에 함수 정의 추가로 이어지는) 라인을 따르는 것입니다

그러나 더 중요한 것은 프로토콜이 여러 기능(예: 반복)에 걸쳐 복잡한 기능을 설명하는 동작을 설명하지 않는다는 것입니다. 해당 반복의 동작은 추상 유형(예: 정렬 또는 정렬)으로 설명됩니다. 반면에 프로토콜 + 추상 유형의 조합은 기능(기능 유틸리티 메서드), 동작(고수준 메서드) 또는 둘 다(구현 세부정보 행동 양식).

(e) 프로토콜이 여러 프로토콜을 상속하도록 허용하고(기본적으로 구조적임) 구체적인 유형만큼 많은 추상 유형(예: 다중 추상 상속 없음)을 허용하면 순수 프로토콜 유형, 순수 추상 유형, 및 프로토콜 + 추상 유형.

나는 이것이 위의 SignedUnsigned 문제를 해결한다고 믿습니다.

  • 일반 IntegerProtocol (프로토콜 구조 NumberAddingProtocol , IntegerSteppingProtocol 등을 상속함)에서 하나를 상속하고 AbstractSignedInteger 에서 다른 하나를 상속하는 두 개의 프로토콜을 정의합니다. AbstractUnsignedInteger ).
  • 그러면 Signed 유형의 사용자는 기능(프로토콜에서)과 동작(추상 계층에서) 모두가 보장됩니다.
  • 프로토콜이 없는 AbstractSignedInteger 구체적인 유형은 _어쨌든_ 사용할 수 없습니다.
  • 그러나 흥미롭게도(그리고 위에서 이미 언급한 미래의 기능으로) IntegerSteppingProtocol (사소하고 기본적으로 단일 기능의 별칭)만 구체적인 AbstractUnsignedInteger 주어지면 Signed 에 대해 다른 프로토콜을 구현하여 해결할 수 있습니다. convert 와 같은 경우에도 가능합니다.

기존의 모든 유형을 유지하면서 대부분을 프로토콜 + 추상 유형으로 바꾸고 일부는 순수한 추상 유형으로 남겨둡니다.

편집: (f) 구문 예제(부분 (a) 포함 ).

편집 2 : 수정 약간의 실수 ( :< 대신 <: (), 최대 고정 가난한 선택 Foo 대신 ::Foo )

protocol {T<: Number}(Foo <: AbstractFoo; Bar <: AbstractBar) # Abstract inheritance
    IterableProtocol(::Foo) # Explicit protocol inheritance.

    # Implicit protocol inheritance.
    start(::Bar)
    next(::Bar, state) # These states should really share an anonymous internal type
    done(::Bar, state)

    # Custom method for protocol involving both participants, defines Foo / Bar relationship.
    set(::Foo, ::Bar, v::T)

    # Custom method only on Bar
    bar(::Bar)
end

# Protocols both Foo{T} and Bar{T}.

이 구문의 문제는 다음과 같습니다.

  • 프로토콜에 대한 익명 내부 유형(예: 상태 변수).
  • 반환 유형.
  • 의미론을 효율적으로 구현하기 어렵습니다.

_추상 유형_은 엔터티 무엇인지 정의합니다. _Protocol_은 엔터티 가 수행 하는 작업을 정의합니다. 단일 패키지 내에서 이 두 개념은 서로 교환할 수 있습니다. 엔터티는 _무엇을 _하는지_입니다. 그리고 "추상 유형"이 더 직접적입니다. 그러나 두 패키지 사이에는 차이점이 있습니다. 클라이언트가 "있는 것"은 필요하지 않지만 클라이언트가 "하는 것"은 필요합니다. 여기서 "추상 유형"은 이에 대한 정보를 제공하지 않습니다.

내 생각에 프로토콜은 단일 디스패치 추상 유형입니다. 패키지 확장 및 협력을 도울 수 있습니다. 따라서 엔터티가 밀접하게 관련된 단일 패키지 내에서 추상 유형을 사용하여 개발을 용이하게 합니다(다중 디스패치로 이익을 얻음). 엔터티가 더 독립적인 패키지 간에 구현 노출을 줄이기 위해 프로토콜을 사용합니다.

@mason-bially

매개변수를 이름으로 일치시켜야 하는 이유를 잘 모르겠습니다.

내 말은 위치에 따른 일치와 _반대로 이름으로 일치하는 것을 의미합니다. 이러한 이름은 구조적으로 하위 유형이 지정된 레코드처럼 작동합니다. 우리가 가지고 있다면

protocol Collection{T}
    eltype = T
end

다음 속성을 아무것도이라고 eltype 의 하위 유형입니다 Collection . 이러한 "매개변수"의 순서와 위치는 중요하지 않습니다.

두 프로토콜이 super에 배치된 유형을 제외하고 정확히 일치한다면 어떻게 다른가요? 그리고 더 큰 유형(프로토콜)의 하위 집합 간의 동작에 차이가 있다면 실제로 추상 유형의 목적을 재발명하는 것이 아닌가요?

그것은 정당한 지적입니다. 명명된 매개변수는 실제로 추상 유형의 많은 속성을 다시 가져옵니다. 나는 우리가 프로토콜과 추상 유형을 모두 가질 필요가 있다는 생각으로 시작한 다음 기능을 통합하고 일반화하려고 시도했습니다. 결국, 현재 type Foo <: Bar 를 선언할 때 어떤 수준에서 실제로 한 일은 Foo.super === Bar 입니다. 따라서 연결하려는 다른 키/값 쌍과 함께 이를 직접 지원해야 할 수도 있습니다.

"유형이 추상 클래스를 구현하고 준수 여부를 확인하려는 의도를 선언하도록 합니다."

예, 저는 그 접근 방식을 핵심 기능으로 만드는 것에 반대합니다.

프로토콜이 여러 프로토콜을 상속하도록 허용하면 ... 그리고 많은 추상 유형

이것은 예를 들어 "T는 x, y, z 메소드가 있고 자신을 AbstractArray의 하위 유형으로 선언하는 경우 프로토콜 P의 하위 유형입니다"라고 말하는 것을 의미합니까? 이런 종류의 "프로토콜 + 추상 유형"은 내 super = T 속성 제안에서 얻을 수 있는 것과 매우 유사하다고 생각합니다. 분명히, 내 버전에서는 지금과 같은 계층 구조로 연결하는 방법을 아직 찾지 못했습니다(예: Integer <: Real <: Number ).

(명목) 추상 유형에서 프로토콜을 상속받는 것은 이에 대한 매우 강력한 제약인 것 같습니다. 프로토콜을 구현하지 _않은_ 추상 유형의 하위 유형이 있습니까? 내 직감은 프로토콜과 추상 유형을 직교하는 것으로 유지하는 것이 더 낫다는 것입니다.

protocol {T :< Number}(Foo :< AbstractFoo; Bar :< AbstractBar) # Abstract inheritance
    IterableProtocol(Foo) # Explicit protocol inheritance.

    # Implicit protocol inheritance.
    start(Bar)
...

이 구문을 이해하지 못합니다.

  • 이 프로토콜에 이름이 있습니까?
  • { }( ) 안의 내용은 정확히 무엇을 의미합니까?
  • 이 프로토콜을 어떻게 사용합니까? 당신은 그것에 파견 할 수 있습니까? 그렇다면 프로토콜이 여러 유형과 관련되어 있다고 가정할 때 f(x::ThisProtocol)=... 정의하는 것은 무엇을 의미합니까?

eltype이라는 속성이 있는 모든 것은 Collection의 하위 유형입니다. 이러한 "매개변수"의 순서와 위치는 중요하지 않습니다.

아, 오해가 있었군요. 더 이해가 가네요. 즉, 다음을 할당할 수 있는 기능:

el1type = el_type
el2type = el_type

내 예제 문제를 해결하기 위해.

따라서 연결하려는 다른 키/값 쌍과 함께 이를 직접 지원해야 할 수도 있습니다.

그리고 이 키/값 기능은 추상을 대체할 것이기 때문에 모든 유형에 있습니다. 좋은 일반적인 솔루션입니다. 귀하의 솔루션은 이제 나에게 훨씬 더 의미가 있습니다.

분명히, 내 버전에서는 지금과 같은 계층 구조로 연결하는 방법을 아직 찾지 못했습니다(예: Integer <: Real <: Number).

super (예: Integer 의 super as Real )를 사용한 다음 super 특별하게 만들고 명명된 유형처럼 행동하거나 사용자 정의 유형 확인 코드(ala python)를 추가하고 super 매개변수에 대한 기본 규칙을 만드는 방법입니다.

(명목) 추상 유형에서 프로토콜을 상속받는 것은 이에 대한 매우 강력한 제약인 것 같습니다. 프로토콜을 구현하지 않은 추상 유형의 하위 유형이 있습니까? 내 직감은 프로토콜과 추상 유형을 직교하는 것으로 유지하는 것이 더 낫다는 것입니다.

예, 추상 제약 조건은 완전히 선택 사항이었습니다! 내 요점은 프로토콜과 추상 유형이 직교한다는 것입니다. 추상 + 프로토콜을 사용하여 특정 동작 및 관련 기능의 조합을 얻을 수 있는지 확인합니다. 기능(유틸리티 기능용)만 원하거나 동작만 원하면 직각으로 사용합니다.

이 프로토콜에 이름이 있습니까?

두 개의 이름을 가진 두 개의 프로토콜( FooBar )은 하나의 블록에서 비롯되지만 매크로를 사용하여 이와 같은 여러 정의를 확장하는 데 익숙합니다. 내 구문의 이 부분은 (a) 부분을 ​​해결하려는 시도였습니다. 이를 무시하면 첫 번째 줄은 단순히 protocol Foo{T <: Number, Bar <: AbstractBar} <: AbstractFoo ( Bar 프로토콜에 대한 별도의 정의 포함). 또한 Number , AbstractBarAbstractFoo 는 일반 유형 정의와 같이 선택 사항입니다.

{ } 및 ( ) 안의 내용은 정확히 무엇을 의미합니까?

{} 는 표준 매개변수 유형 정의 섹션입니다. 예를 들어 Float64 를 사용하여 Foo 프로토콜을 구현하는 유형을 설명하기 위해 Foo{Float64} 를 사용할 수 있습니다. () 는 기본적으로 프로토콜 본문에 대한 변수 바인딩 목록입니다(따라서 여러 프로토콜을 한 번에 설명할 수 있음). 원본에 <: 대신 :< 를 잘못 입력했기 때문에 혼동을 일으킨 것은 제 잘못 <: 있습니다. <<name>> <<parametric>> <<bindings>> 구조를 유지하도록 교체하는 것도 가치가 있습니다. 여기서 <<name>> 는 때때로 바인딩 목록이 될 수 있습니다.

이 프로토콜을 어떻게 사용합니까? 당신은 그것에 파견 할 수 있습니까? 그렇다면 프로토콜이 여러 유형과 관련되어 있다고 가정할 때 f(x::ThisProtocol)=... 정의하는 것은 무엇을 의미합니까?

귀하의 디스패치 예제는 제 생각에는 구문상 올바른 것 같습니다. 실제로 다음 정의를 고려하십시오.

protocol FooProtocol # Single protocol definition shortcut
    foo(::FooProtocol) # I changed my syntax here, protocol names inside the protocol block should referenced as types
end

abstract FooAbstract

# This next line could use better syntax, like a type alias with an Intersection or something.
protocol Foo <: FooAbstract
    FooProtocol(::Foo)
end

type Bar <: FooAbstract
  a
end

type Baz
  b
end

type Bax <: FooAbstract
  c
end

f(f::Any) = ... # def (0)

foo(x::Bar) = ... # def (1a)
foo(x::Baz) = ... # def (1b)

f(x::FooProtocol) = ... # def (2); Least specific type (structural)

f(Bar(...)) # Would call def (2)
f(Baz(...)) # Would call def (2)
f(Bax(...)) # Would call def (0)

f(x::FooAbstract) = ... # def (3); Named type, more specific than structural

f(Bar(...)) # Would call def (3)
f(Baz(...)) # Would call def (2)
f(Bax(...)) # Would call def (3)

f(x::Foo) = ... # def (4); Named structural type, more specific than equivalent named type

f(Bar(...)) # Would call def (4)
f(Baz(...)) # Would call def (2)
f(Bax(...)) # Would call def (3)

구조를 확인할 보다 구체적인 추상 유형이 제공되지 않는 한 프로토콜은 명명된 Top 유형(Any)을 효과적으로 사용합니다. 실제로 프로토콜 구문을 사용하는 대신 typealias Foo Intersect{FooProtocol, Foo} (_Edit: Intersect가 잘못된 이름이었습니다. 아마도 Join 대신 Intersect가 처음이었을 것입니다_)와 같은 것을 허용하는 것이 가치가 있습니다.

아, 좋아, 그게 지금 나에게 훨씬 더 의미가 있습니다! 동일한 블록에서 여러 프로토콜을 함께 정의하는 것은 흥미롭습니다. 나는 그것에 대해 좀 더 생각해야 할 것입니다.

몇 분 전에 모든 예제를 정리했습니다. 스레드의 앞부분에서 누군가가 아이디어를 테스트하기 위해 프로토콜 모음을 수집한다고 언급했는데, 저는 그것이 좋은 생각이라고 생각합니다.

동일한 블록에 있는 여러 프로토콜은 언어를 로드할 때 정의/컴파일에서 양쪽 모두에 올바른 유형 주석이 있는 개체 간의 복잡한 관계를 설명하려고 할 때 일종의 소심한 표정입니다(예: python, Java는 예를 들어 ' 문제가 있습니다). 반면에, 그들 대부분은 아마도 다중 방법을 사용하여 쉽게 고칠 수 있고 유용성 면에서 현명할 것입니다. 그러나 성능 고려 사항은 프로토콜 내에 올바르게 입력된 기능에서 나올 수 있습니다(vtables에 따라 프로토콜을 전문화하여 프로토콜 최적화).

이전에 ::Any 사용하는 방법으로 프로토콜을 (의도적으로) 구현할 수 있다고 언급했습니다. 제 생각에는 이는 단순히 무시할 수 있는 아주 간단한 경우라고 생각합니다. 구현 메서드가 ::Any 에 전달된 경우 구체적인 유형은 프로토콜로 분류되지 않습니다. 반면에 나는 이것이 반드시 문제라고 팔지 않는다.

우선 ::Any 메소드가 사실 이후에 추가되면(예를 들어 누군가 이를 처리하기 위한 보다 일반적인 시스템을 생각해 냈기 때문에) 이는 여전히 유효한 구현이며, 프로토콜을 최적화 기능으로 사용하는 경우에도 ::Any 디스패치 메소드의 특수 버전은 여전히 ​​성능 향상을 위해 작동합니다. 그래서 결국 나는 실제로 그들을 무시하는 것에 반대합니다.

그러나 프로토콜 정의자가 두 가지 옵션 중에서 선택할 수 있도록 하는 구문을 갖는 것은 가치가 있을 것입니다. 첫 번째로 ::Any 디스패치 메소드에 대한 전달 구문은 global 키워드를 말합니다(다음 섹션 참조). 더 구체적인 방법이 필요한 두 번째 방법은 기존의 유용한 키워드가 생각나지 않습니다.

편집 : 무의미한 것들을 제거했습니다.

Join 는 정확히 프로토콜 유형의 교차점입니다. 바로 "만남"입니다. 그리고 다행스럽게도 Join 유형은 필요하지 않습니다. 프로토콜 유형이 이미 교차로에서 닫혀 있기 때문입니다. 교차를 계산하려면 연결된 두 메서드 목록과 함께 새 프로토콜 유형을 반환하기만 하면 됩니다.

나는 프로토콜이 ::Any 정의에 의해 하찮아지는 것에 대해 너무 걱정하지 않습니다. 나에게 " Any 제외하고 일치하는 정의 찾기"라는 규칙은 Occam의 면도날에 위배됩니다. 서브타이핑 알고리즘을 통해 "임시 무시" 플래그를 스레딩하는 것은 꽤 성가신 일이라는 점은 말할 것도 없습니다. 결과 알고리즘이 일관성이 있는지조차 확신할 수 없습니다.

나는 프로토콜에 대한 아이디어를 매우 좋아합니다. (CLUster가 조금 생각납니다), 이것이 JuliaCon에서 Jeff가 논의한 새로운 하위 유형과 특성에 어떻게 맞을지 궁금합니다. (내가 Julia에서 여전히 보고 싶은 두 가지).

이렇게 하면 고유한 하위 유형 지정 규칙이 있는 새로운 유형의 유형이 추가됩니다(https://github.com/JuliaLang/julia/issues/6975#issuecomment-160857877). 언뜻 보기에는 시스템의 나머지 부분과 호환되는 것처럼 보이며 그냥 꽂기만 하면 됩니다.

이 프로토콜은 거의 @mauro3 의 특성의 "하나의 매개변수" 버전입니다.

Join 는 정확히 프로토콜 유형의 교차점입니다.

나는 그것이 교차로라고 말했을 때 내가 틀렸다는 것을 어떻게 든 스스로 확신했습니다. 비록 Union 와 같이 한 줄에서 유형을 교차시키는 방법이 여전히 필요하지만.

편집하다:

나는 여전히 프로토콜과 추상 유형을 하나의 시스템으로 일반화하고 해결을 위한 사용자 정의 규칙을 허용하는 것을 좋아합니다(예: 현재 추상 유형 시스템을 설명하는 super ). 올바르게 수행된다면 사람들이 사용자 정의 유형 시스템을 추가하고 결국 해당 유형 시스템에 대한 사용자 정의 최적화를 추가할 수 있을 것이라고 생각합니다. protocol이 올바른 키워드인지 확신할 수 없지만 적어도 abstract 를 매크로로 전환할 수 있다면 좋을 것입니다.

밀밭에서: 대상으로 일반화를 추구하는 것보다 프로토콜 및 추상화를 통해 공통점을 높이는 것이 좋습니다.

무엇?

프로토콜과 추상 유형의 의도, 능력 및 잠재력을 일반화하는 프로세스는 질적으로 가장 만족스러운 합성을 해결하는 방법이 덜 효과적입니다. 목적, 패턴, 프로세스의 본질적인 공통점을 먼저 수집하는 것이 더 효과적입니다. 그리고 그 이해를 발전시켜 종합을 형성하는 관점의 개선을 허용하십시오.

Julia에게 유익한 실현이 무엇이든, 그것은 합성이 제공하는 비계 위에 구성됩니다. 더 명확한 합성은 건설적인 힘과 유도력입니다.

뭐?

나는 그가 우리가 프로토콜에서 원하는 것과 그것이 왜 유용한지 먼저 파악해야 한다고 말하는 것 같아요. 그런 다음 해당 유형과 추상 유형이 있으면 일반 유형을 통합하는 것이 더 쉬울 것입니다.

단순한 프로토콜

(1) 옹호

프로토콜은 (보다 정교한) 프로토콜이 되도록 확장될 수 있습니다.
프로토콜은 (덜 정교한) 프로토콜이 되도록 축소될 수 있습니다.
프로토콜은 [소프트웨어에서] 일치하는 인터페이스로 실현될 수 있습니다.
인터페이스의 적합성을 결정하기 위해 프로토콜을 쿼리할 수 있습니다.

(2) 제안

프로토콜은 기본적으로 프로토콜별 버전 번호를 지원해야 합니다.

다음과 같은 방법을 지원하는 것이 좋습니다.
인터페이스가 프로토콜을 준수하면 true로 응답합니다. 인터페이스 할 때
프로토콜의 하위 집합에 충실하고 확장된 경우 적합합니다.
불완전하게 응답하고 그렇지 않으면 false로 응답합니다. 함수는 모든 항목을 나열해야 합니다.
프로토콜을 작성하는 불완전한 인터페이스에 필요한 보강.

(3) 사색하다

프로토콜은 고유한 종류의 모듈일 수 있습니다. 그것의 수출은 봉사할 것입니다
일부 인터페이스가 준수하는지 여부를 결정할 때 초기 비교 대상으로 사용합니다.
[내보낸] 유형 및 기능으로 지정된 모든 프로토콜은 다음을 사용하여 선언할 수 있습니다.
타고난 추상화를 지원하는 @abstract , @type , @immutable@function .

[pao: 코드 따옴표로 전환합니다. 하지만 나중에 이 작업을 수행할 때 말이 이미 헛간을 떠났다는 점에 유의하세요...]

( @mentions 를 인용해야 합니다!)

감사합니다 - 수정

2015년 12월 16일 수요일 오전 3시 1분에 Mauro [email protected] 다음과 같이 썼습니다.

(@멘션을 인용해야 합니다!)


이 이메일에 직접 답장하거나 GitHub에서 확인하세요.
https://github.com/JuliaLang/julia/issues/6975#issuecomment -165026727.

죄송합니다. 더 명확해야 합니다. "가 아닌 `를 사용하는 코드 인용

인용 수정을 수정했습니다.

감사합니다 -- 나의 사전 무지를 용서하십시오

프로토콜 유형 추가에 대한 최근 논의를 이해하려고 노력했습니다. 내가 뭔가를 잘못 이해하고 있는 것일 수도 있지만 프로토콜이 설명하려는 관련 추상 유형의 이름을 사용하는 대신 명명된 프로토콜을 사용해야 하는 이유는 무엇입니까?

내 관점에서 유형에서 예상되는 동작을 설명하는 어떤 방법으로 현재 추상 유형 시스템을 확장하는 것은 매우 자연스럽습니다. 이 스레드에서 처음 제안된 것과 비슷하지만 아마도 Jeff 구문을 사용하는 것일 수 있습니다.

abstract Iterable
    start(::_)
    done(::_, state)
    next(::_, state)
end

이 경로를 사용할 때 하위 유형이 인터페이스를 구현한다고 특별히 표시할 필요가 없습니다. 이것은 하위 입력을 통해 암시적으로 수행됩니다.

명시적 인터페이스 메커니즘의 주요 목표는 더 나은 오류 메시지를 얻고 더 나은 검증 테스트를 수행하기 위한 IMHO입니다.

따라서 다음과 같은 유형 선언:

type Foo <: Iterable
  ...
end

... 섹션에서 함수를 정의합니까? 그렇지 않은 경우 누락된 기능(및 이와 관련된 복잡성)에 대해 언제 오류가 발생합니까? 또한 다중 프로토콜을 구현하는 유형은 어떻게 됩니까? 다중 추상 상속을 활성화합니까? 슈퍼 메서드 해결을 어떻게 처리합니까? 이것은 다중 디스패치로 무엇을합니까? 첫 번째 유형이 정의된 후 메소드에 대한 새로운 유형 전문화를 어떻게 정의합니까? 유형을 정의한 후 프로토콜을 어떻게 정의합니까?

이러한 질문은 모두 새로운 유형을 생성(또는 새로운 유형 공식 생성)하면 더 쉽게 해결할 수 있습니다.

각 프로토콜에 대해 반드시 관련된 추상 유형이 있는 것은 아닙니다(실제로는 없을 것입니다). 동일한 유형으로 현재 인터페이스의 여러 개를 구현할 수 있습니다. 현재 추상 유형 시스템으로 설명할 수 없는 것입니다. 따라서 문제.

  • 추상 다중 상속(여러 프로토콜 구현)은 이 기능과 직교합니다(위 Jeff가 언급한 대로). 따라서 프로토콜이 언어에 추가되었다고 해서 해당 기능을 얻는 것은 아닙니다.
  • 다음 의견은 인터페이스를 확인하는 시점에 대한 질문입니다. 나는 이것이 Julian처럼 느껴지지 않는 블록 내 함수 정의에 연결될 필요가 없다고 생각합니다. 대신 세 가지 간단한 솔루션이 있습니다.

    1. #7025에서 구현된 것처럼 모든 함수 정의 후에 또는 단위 테스트에서 호출할 수 있는 verify_interface 메서드를 사용합니다.

    2. 인터페이스를 전혀 확인할 수 없으며 "MethodError"의 개선된 오류 메시지로 이를 연기할 수 있습니다. 사실 이것은 1에 대한 좋은 대안입니다.

    3. 컴파일 시간 단위가 끝날 때 또는 모듈 로드 단계가 끝날 때 모든 인터페이스를 확인하십시오. 현재 다음을 가질 수도 있습니다.

function a()
  b()
end

function b()
end

따라서 블록 내 함수 정의가 여기에 필요하지 않다고 생각합니다.

  • 마지막 요점은 추상 유형에 연결되지 않은 프로토콜이 있을 수 있다는 것입니다. 이것은 현재 확실히 사실입니다(예: 비공식 "Iterable" 프로토콜). 그러나 내 관점에서 이것은 다중 추상 상속이 없기 때문입니다. 이것이 우려되는 경우 이를 해결하기 위한 새로운 언어 기능을 추가하는 대신 추상 다중 상속을 추가하도록 합시다. 또한 여러 인터페이스를 구현하는 것이 절대적으로 중요하며 이는 Java/C#에서 절대적으로 일반적이라고 생각합니다.

"프로토콜"과 같은 것과 다중 상속의 차이점은 프로토콜이 정의된 후에 유형을 추가할 수 있다는 것입니다. 이것은 패키지(프로토콜 정의)가 기존 유형과 함께 작동하도록 하려는 경우에 유용합니다. 생성 후 유형의 상위 유형을 수정하는 것을 허용할 수 있지만 그 시점에서 "프로토콜" 또는 이와 유사한 것으로 부르는 것이 더 나을 것입니다.

흠 그래서 기존 유형에 대한 대체/향상된 인터페이스를 정의할 수 있습니다. 이것이 실제로 필요한 곳이 어디인지 아직 명확하지 않습니다. 기존 인터페이스에 무언가를 추가하려는 경우(OP에서 제안한 접근 방식을 따를 때) 단순히 하위 유형을 지정하고 하위 유형에 추가 인터페이스 메소드를 추가합니다. 이것이 그 접근 방식의 좋은 점입니다. 그것은 꽤 잘 확장됩니다.

예: 유형을 직렬화하는 패키지가 있다고 가정합니다. tobits 메소드는 유형에 대해 구현되어야 하며, 그러면 해당 패키지의 모든 기능이 해당 유형과 함께 작동합니다. 이것을 Serializer 프로토콜이라고 부르겠습니다(예: tobits 가 정의됨). 이제 tobits 를 구현하여 Array (또는 다른 유형)을 추가할 수 있습니다. 다중 상속을 사용하면 정의 후에 Array 상위 유형을 추가할 수 없으므로 ArraySerialzer 와 함께 사용할 수 없습니다. 이것은 중요한 사용 사례라고 생각합니다.

알겠습니다. 이해해 주세요. https://github.com/JuliaLang/IterativeSolvers.jl/issues/2 는 솔루션이 기본적으로 오리 타이핑을 사용하는 유사한 문제입니다. 이 문제를 우아하게 해결할 수 있는 것이 있다면 정말 좋을 것입니다. 그러나 이것은 파견 수준에서 지원되어야 하는 것입니다. 위의 프로토콜 아이디어를 올바르게 이해한다면 추상 유형이나 프로토콜을 함수의 유형 주석으로 넣을 수 있습니다. 여기에서 이 두 개념을 충분히 강력한 단일 도구로 병합하는 것이 좋습니다.

동의합니다. 추상 유형과 프로토콜을 모두 사용하는 것은 매우 혼란스러울 것입니다. 제 기억이 맞다면 위에서 추상 유형에는 프로토콜로 모델링할 수 없는 의미 체계가 있다고 주장했습니다. 즉 추상 유형에는 프로토콜에 없는 기능이 있습니다. 그것이 필연적인 경우라 할지라도(나는 확신하지 못한다), 두 개념 사이에 큰 중첩이 있기 때문에 여전히 혼란스러울 것입니다. 따라서 프로토콜을 위해 추상 유형을 제거해야 합니다.

프로토콜에 대해 위의 합의가 있는 한 인터페이스 지정을 강조합니다. 추상 유형은 없는 프로토콜 중 일부를 수행하는 데 사용되었을 수 있습니다. 그것이 가장 중요한 용도라는 의미는 아닙니다. 프로토콜이 무엇이고 그렇지 않은지 알려주세요. 그러면 추상 유형이 어떻게 다른지, 그리고 어떤 프로토콜을 가져오는지 알려드릴 수 있습니다. 나는 추상 유형을 유형학만큼 인터페이스에 대해 고려한 적이 없습니다. 유형학적 유연성에 대한 자연스러운 접근 방식을 버리는 것은 비용이 많이 듭니다.

@JeffreySarnoff +1

숫자 유형 계층을 생각해 보십시오. Signed, Unsigned와 같은 다른 추상 유형은 인터페이스에 의해 정의되지 않습니다. "Unsigned"를 정의하는 메소드 세트는 없습니다. 매우 유용한 선언입니다.

문제가 보이지 않습니다. SignedUnsigned 유형이 모두 동일한 메소드 세트를 지원하는 경우 동일한 인터페이스를 가진 두 개의 프로토콜을 작성할 수 있습니다. 또, 같은 유형 선언 Signed 보다는 Unsigned (다르게 동일한 기능 법 즉, 메소드) 디스패치에 사용할 수있다. 여기서 핵심은 유형이 구현하는 메서드를 기반으로 암시적으로 이를 감지하기보다는 유형이 프로토콜을 구현한다고 고려하기 전에 명시적 선언을 요구하는 것입니다.

그러나 https://github.com/JuliaLang/julia/issues/6975#issuecomment -168499775에서와 같이 암시적으로 연결된 프로토콜을 갖는 것도 중요합니다.

프로토콜은 호출할 수 있는 기능을 정의할 수 있을 뿐만 아니라 보유해야 하는 속성을 (암시적으로 또는 기계 테스트 가능한 방식으로) 문서화할 수도 있습니다. 와 같은:

abs(x::Unsigned) == x
signbit(x::Unsigned) == false
-abs(x::Signed) <= 0

SignedUnsigned 사이의 이 외부적으로 보이는 행동 차이는 이 구분을 유용하게 만드는 것입니다.

"추상적"이어서 적어도 이론적으로는 외부에서 즉시 확인할 수 없는 유형 간에 차이가 있는 경우 올바른 선택을 하기 위해 유형의 구현을 알아야 할 가능성이 높습니다. 여기에서 현재 abstract 가 유용할 수 있습니다. 이것은 아마도 대수적 데이터 유형의 방향으로 갈 것입니다.

프로토콜을 단순히 유형을 그룹화하는 데 사용하지 않아야 할 이유가 없습니다. 즉, 정의된 메서드가 필요하지 않습니다(그리고 트릭을 사용하여 "현재" 디자인에서 가능합니다: https://github.com/JuliaLang/julia/issues/ 6975#issuecomment-161056795). (또한 이것은 암시적으로 정의된 프로토콜을 방해하지 않습니다.)

(Un)signed 예를 고려하면: Signed 유형이 있지만 어떤 이유로 인해 다른 추상 유형의 하위 유형이기도 해야 하는 경우 어떻게 해야 합니까? 이것은 불가능합니다.

@eschnett : 현재 추상 유형은 하위 유형의 구현과 관련이 없습니다. 논의되었지만: #4935.

대수 데이터 유형은 연속적인 구체화가 본질적으로 의미가 있는 좋은 예입니다.
모든 분류는 프로토콜 사양의 멜란지보다 훨씬 더 자연스럽게 주어지며 추상 유형 계층으로 더 직접적으로 유용합니다.

둘 이상의 추상 유형 계층 구조의 하위 유형인 유형을 갖는 것에 대한 참고 사항도 중요합니다. 추상화의 다중 상속과 함께 제공되는 상당한 실용주의적 힘이 있습니다.

@mauro3 네,

예를 들어, 익명 요소가 있는 튜플은 다음과 같습니다.

DiscriminatedUnion{Int16, UInt32, Float64}

또는 명명된 요소 사용:

discriminated_union MyType
    i::Int16
    u::UInt32
    f::Float64
end

내가 말하려고 했던 요점은 추상 유형이 그러한 구조를 Julia에 매핑하는 좋은 방법 중 하나라는 것입니다.

프로토콜을 단순히 유형을 그룹화하는 데 사용해서는 안 되는 이유가 없습니다. 즉, 정의된 메서드가 필요하지 않습니다(그리고 트릭을 사용하는 "현재" 디자인에서 가능합니다: #6975(주석)). (또한 이것은 암시적으로 정의된 프로토콜을 방해하지 않습니다.)

성능을 달성하려면 이것에 주의해야 할 것 같은데, 고려 사항이 많지 않아 자주 고려하는 것 같습니다. 예제에서 컴파일러가 컴파일 타임에 함수를 선택할 수 있도록 단순히 버전이 아닌 것을 정의하고 싶을 것입니다(런타임에 올바른 함수를 선택하기 위해 함수를 호출하거나 컴파일러가 함수를 검사하는 대신 결과를 결정하기 위해). 개인적으로 여러 추상 "상속"을 태그로 사용하는 것이 더 나은 솔루션이라고 생각합니다.

나는 그것이이 매크로의 이상한 해킹 같은 느낌 것입니다 매크로에 싸여 할 수 있지만 우리는 (최소한으로 필요한 트릭과 타입 시스템의 지식을 유지해야한다고 생각 우리는 내가 생각하고 형식 시스템을 조작하는 매크로를 사용하는 경우 @ JeffBezanson통합 솔루션 이 이 문제를 더 잘 해결할 것입니다).

(Un)signed 예를 고려하면: Signed 유형이 있지만 어떤 이유로 다른 추상 유형의 하위 유형이기도 해야 하는 경우 어떻게 해야 합니까? 이것은 불가능합니다.

다중 추상 상속.


나는 이 모든 근거가 이전에 다루어졌다고 생각합니다. 이 대화는 원을 그리며 진행되는 것으로 보입니다(매번 더 긴밀한 원이지만). 코퍼스나 프로토콜을 사용하는 문제를 습득해야 한다고 언급한 것 같습니다. 이렇게 하면 솔루션을 더 쉽게 판단할 수 있습니다.

반복하는 동안 :) 추상 유형은 명목적인 반면 프로토콜은 구조적이라는 사실을 모든 사람에게 상기시키고 싶습니다. 그래서 나는 그것들을 직교로 취급하는 디자인을 선호합니다. 그러나 우리가 실제로 프로토콜에서 추상 유형의 수용 가능한 "인코딩"을 생각해낼 수 있지 않는 한 (아마도 관련 유형을 영리하게 사용하여). 물론 다중 추상 상속도 생성하는 경우 보너스 포인트. 나는 이것이 가능하다고 생각하지만 우리는 아직 거기에 도달하지 못했습니다.

@JeffBezanson "관련 유형"은 "[a] 프로토콜과 관련된 구체적인 유형"과 구별됩니까?

네 그렇게 믿습니다. 프로토콜이 메서드를 지정하는 것과 같은 방식으로 "값"이 유형인 일부 키-값 쌍을 지정하는 프로토콜의 기술적인 의미에서 "연관 유형"을 의미합니다. 예를 들어 "유형 Foo는 eltype 가 있는 경우 컨테이너 프로토콜을 따릅니다." 또는 "유형 Foo는 ndims 매개변수가 2인 경우 매트릭스 프로토콜을 따릅니다."

추상 유형은 명목적인 반면 프로토콜은 구조적이며
프로토콜이 작동하는 동안 추상 유형은 정성적이며
프로토콜이 수행되는 동안 추상 유형(다중 상속 포함)이 조정됩니다.

하나의 인코딩이 다른 하나에 있더라도 "안녕하세요, 안녕 .. 잘 지내? 가자!" Julia는 일반적으로 목적이 있는 프로토콜 개념과 다중 상속 가능한 추상 유형(일반 목적 개념)을 모두 명확하게 제시해야 합니다. Julia에게 두 가지 모두를 제공하는 교묘한 전개가 따로따로 접혀 있다면, 그것은 하나와 다른 하나보다 그렇게 행해질 가능성이 더 큽니다.

@mason-bially: 다중 상속도 추가해야 하나요? 이것은 여전히 ​​유형 생성 후에 상위 유형을 추가할 수 없다는 문제를 남깁니다(허용되지 않는 한).

@JeffBezanson : 순전히 명목상의 프로토콜을 허용하는 것을 막을 수 있는 것은 없습니다.

@mauro3 사후 수퍼타입 삽입을 허용할지 여부에 대한 결정이 다중 상속에 연결되어야 하는 이유는 무엇입니까? 그리고 다른 종류의 슈퍼타입 생성이 있습니다. 일부는 새로운 것이 무엇이든 간에 삽입할 수 있는 능력을 가정하는 명백히 무해합니다. 저는 Real과 AbstractFloat 사이에 추상 유형을 추가하고 싶었습니다. Double floats 및 system Floats는 AbstractFloat의 하위 유형으로 존재하는 시스템 Floats를 방해하지 않고 함께 합니다. 아마도 허용하기 쉽지 않은 것은 Integer의 현재 하위 유형을 세분화하는 기능이므로 ".. define f(Bool) before.." 메시지가 많이 발생하지 않도록 해야 합니다. 또는 Integer의 하위 유형인 Signed의 상위 유형을 도입하고 예를 들어 서수를 투명하게 처리할 수 있도록 숫자 계층을 엽니다.

원의 다른 라운드를 시작했다면 죄송합니다. 주제는 매우 복잡하며 솔루션이 사용하기 매우 간단해야 합니다. 따라서 다음을 다루어야 합니다.

  • 일반 솔루션
  • 성능 저하 없음
  • 사용 용이성(또한 이해하기 쉽습니다!)

#6975에서 처음에 제안된 것이 나중에 논의되는 프로토콜 아이디어와 상당히 다르기 때문에 프로토콜이 어떻게 생겼는지 설명하는 일종의 JEP를 갖는 것이 좋을 수 있습니다.

현재 0.4(매크로 없음)를 사용하여 형식 인터페이스를 정의하고 유효성을 검사하는 방법의 예는 gf.c에 수정 사항이 없는 한 현재 디스패치에는 특성 스타일 디스패치에 의존합니다. 이것은 유효성 검사를 위해 생성된 함수를 사용하며 모든 유형 계산은 유형 공간에서 수행됩니다.

제공된 유형이 날짜의 반복자인지 확인해야 하는 위치를 정의하는 DSL에서 이것을 런타임 검사로 사용하기 시작했습니다.

현재 수퍼 유형의 다중 상속을 지원하며 _super 필드 이름은 런타임에서 사용되지 않으며 모든 유효한 기호가 될 수 있습니다. _super Tuple에 n개의 다른 유형을 제공할 수 있습니다.

https://github.com/mdcfrancis/tc.jl/blob/master/test/runtests.jl

https://github.com/JuliaLang/julia/issues/5#issuecomment -230645040에서 가능한 구문에 대한 JuliaCon의 토론에 대한 후속 조치를 여기에서 지적했습니다.

Guy Steele은 다중 디스패치 언어(Fortress)의 특성에 대한 좋은 통찰력을 가지고 있습니다 . JuliaCon 2016 기조 연설:

몇 가지 하이라이트: 대수적 속성에 대한 큰 특성 시스템, 특성을 구현하는 유형에 대한 특성 속성의 단위 테스트, 구현한 시스템이 너무 복잡할 수 있으며 이제 더 간단한 작업을 수행할 것입니다.

프로토콜에 대한 tensorflow 컴파일러 AD 사용 사례를 위한 새로운 Swift:
https://gist.github.com/rxwei/30ba75ce092ab3b0dce4bde1fc2c9f1d
@timholy@Keno 가 관심을

이 문제에 대한 디자인 공간을 탐색할 때 이 프레젠테이션에 주의를 기울일 필요가 있다고 생각

구체적이지 않은 아이디어와 관련 배경 작업에 대한 링크에 대한 토론을 위해서는 해당 담론 스레드를 시작하여 게시하고 토론하는 것이 좋습니다.

정적으로 유형이 지정된 언어의 일반 프로그래밍에 대한 연구에서 발생하고 논의된 거의 모든 문제는 Julia와 관련이 없습니다. 정적 언어는 원하는 코드를 작성할 수 있는 충분한 표현력을 제공하는 동시에 유형 시스템 위반이 없는지 정적으로 유형 검사할 수 있는 문제에 거의 전적으로 관심이 있습니다. 표현력에 문제가 없고 정적 유형 검사가 필요하지 않으므로 Julia에서는 그 중 어느 것도 실제로 중요하지 않습니다.

우리가 관심을 갖는 것은 사람들이 프로토콜의 기대치를 구조화된 방식으로 문서화하여 언어가 동적으로 확인할 수 있도록 하는 것입니다(가능한 경우 사전에). 우리는 또한 사람들이 특성과 같은 것에 대해 파견할 수 있도록 하는 데에도 신경을 쓰고 있습니다. 연결해야 하는지 여부는 열려 있습니다.

결론: 정적 언어로 된 프로토콜에 대한 학문적 연구는 일반적으로 관심이 있을 수 있지만 Julia의 맥락에서는 별로 도움이 되지 않습니다.

우리가 관심을 갖는 것은 사람들이 프로토콜의 기대치를 구조화된 방식으로 문서화하여 언어가 동적으로 확인할 수 있도록 하는 것입니다(가능한 경우 사전에). 우리는 또한 사람들이 특성과 같은 것에 대해 파견할 수 있도록 하는 데에도 신경을 쓰고 있습니다. 연결해야 하는지 여부는 열려 있습니다.

_그게_ :티켓:

주요 변경 사항을 피하는 것 외에도 추상 유형을 제거하고 golang 스타일 암시적 인터페이스를 도입하는 것이 julia에서 실현 가능합니까?

아니, 그렇지 않을 것이다.

글쎄요, 그것이 프로토콜/특성의 전부가 아닙니까? 프로토콜이 암시적이어야 하는지 명시적이어야 하는지에 대한 논의가 있었습니다.

0.3(2014) 이후 경험에 따르면 암시적 인터페이스(즉, 언어/컴파일러에 의해 적용되지 않음)가 제대로 작동하는 것으로 나타났습니다. 또한 일부 패키지가 어떻게 진화했는지를 목격하면서 최고의 인터페이스가 유기적으로 개발되었으며 나중에야 공식화(=문서화)되었다고 생각합니다.

언어에 의해 강제되는 인터페이스에 대한 형식적인 설명이 필요한지 잘 모르겠습니다. 그러나 그것이 결정되는 동안 다음을 권장하는 것이 좋습니다(문서, 자습서 및 스타일 가이드에서).

  1. "인터페이스"는 저렴하고 가벼우며 유형 집합에 대해 규정된 동작을 가진 함수의 집합입니다(예, 유형은 적절한 수준의 세분성입니다. x::T 경우 T 이면 충분해야 합니다. x 가 인터페이스를 구현하는지 여부를 결정하기 위해) . 따라서 확장 가능한 동작으로 패키지를 정의하는 경우 인터페이스를 문서화 하는 것이 좋습니다.

  2. 인터페이스 는 하위 유형 관계로 설명할 필요가 없습니다 . 공통(사소하지 않은) 상위 유형이 없는 유형은 동일한 인터페이스를 구현할 수 있습니다. 유형은 여러 인터페이스를 구현할 수 있습니다.

  3. 전달/구성에는 암시적으로 인터페이스가 필요합니다. "랩퍼가 부모의 모든 메서드를 상속하도록 하는 방법"은 자주 나오는 질문이지만 올바른 질문은 아닙니다. 실용적인 솔루션은 핵심 인터페이스를 갖고 래퍼에 대해 구현하는 것입니다.

  4. 특성은 저렴 하며 자유롭게 사용해야 합니다. Base.IndexStyle 는 훌륭한 표준 예입니다.

모범 사례가 무엇인지 잘 모르기 때문에 다음과 같은 설명이 도움이 될 것입니다.

  1. 인터페이스에 개체가 인터페이스를 구현하는지 여부를 결정하기 위해 예를 들어 Tables.istable 와 같은 쿼리 기능이 있어야 합니까? 호출자가 다양한 대체 인터페이스로 작업할 수 있고 대체 목록을 살펴봐야 하는 경우에는 좋은 방법이라고 생각합니다.

  2. docstring에서 인터페이스 문서를 위한 가장 좋은 위치는 무엇입니까? 위의 쿼리 기능을 말합니다.

  1. 예, 유형은 적절한 수준의 세분성입니다.

왜 이렇게이다? 유형의 일부 측면은 반복과 같은 (디스패치 목적을 위해) 인터페이스로 인수분해될 수 있습니다. 그렇지 않으면 코드를 다시 작성하거나 불필요한 구조를 부과해야 합니다.

  1. 인터페이스 는 하위 유형 관계로 설명할 필요가 없습니다 .

아마도 그것은 필요하지 않지만 더 낫지 않을까요? 반복 가능한 유형에 대한 함수 디스패치를 ​​가질 수 있습니다. 타일링된 반복 가능한 유형이 이를 암시적으로 충족해야 하지 않습니까? 인터페이스에만 관심이 있는데 왜 사용자가 명목 유형 주위에 이것을 그려야 합니까?

본질적으로 추상 인터페이스로 사용하는 경우 명목 하위 유형 지정의 요점은 무엇입니까? 특성은 더 세분화되고 강력해 보이므로 일반화하는 것이 좋습니다. 따라서 유형이 거의 특성인 것처럼 보이지만 그 한계를 해결하려면 특성이 있어야 합니다(반대의 경우도 마찬가지입니다).

본질적으로 추상 인터페이스로 사용하는 경우 명목 하위 유형 지정의 요점은 무엇입니까?

디스패치 - 무언가의 명목 유형에 대해 디스패치할 수 있습니다. 유형이 인터페이스를 구현하는지 여부에 대해 디스패치할 필요가 없는 경우 그냥 오리 유형할 수 있습니다. 이것이 사람들이 일반적으로 Holy 특성을 사용하는 것입니다. 특성을 사용하면 일부 인터페이스가 구현되어 있다고 가정하는 구현을 호출하도록 디스패치할 수 있습니다(예: "알려진 길이"). 사람들이 원하는 것처럼 보이는 것은 간접적인 계층을 피하는 것이지만 그것은 단지 편의일 뿐 필수 사항이 아닌 것처럼 보입니다.

왜 이렇게이다? 유형의 일부 측면은 반복과 같은 (디스패치 목적을 위해) 인터페이스로 인수분해될 수 있습니다. 그렇지 않으면 코드를 다시 작성하거나 불필요한 구조를 부과해야 합니다.

@tpapp 은 모든 인터페이스가 유형 계층으로 표현될 수 있는 것이 아니라 어떤 것이 인터페이스를 구현하는지 여부를 결정하기 위해 유형만 필요하다고 말한 것 같습니다.

MacroToolsforward 를 사용하는 동안 생각해보십시오.

많은 메소드를 전달하는 것은 때때로 짜증난다.

<strong i="9">@forward</strong> Foo.x a b c d ...

Foo.x 의 유형과 메소드 목록을 사용하고 어떤 것을 전달할 것인지 유추할 수 있다면 어떨까요? 이것은 일종의 inheritance 이며 기존 기능(매크로 + 생성된 함수)으로 구현될 수 있으며 일종의 인터페이스처럼 보이지만 언어에서 다른 것이 필요하지 않습니다.

나는 우리가 상속받을 목록을 결코 생각해낼 수 없다는 것을 알고 있습니다(이것이 또한 정적 class 모델이 덜 유연한 이유이기도 합니다), 때때로 당신은 그들 중 몇 개만 필요하지만 핵심 기능에 대해서는 편리합니다( 예를 들어 누군가 Array 주위에 래퍼( AbstractArray 하위 유형)를 정의하려고 하면 대부분의 함수가 전달됩니다.

@datnamer : 다른 사람들이 명확히 세분화되어서는 안 됩니다(즉, 인터페이스 구현은 유형이 지정된 값에 의존해서는 안 됨). 이것은 컴파일러의 최적화 모델과 잘 맞으며 실제로는 제약이 없습니다.

아마도 내가 명확하지 않았을 수도 있지만, 내 응답의 목적은 이미 Julia 에서 유용한 정도로 인터페이스가 있으며, 인터페이스가 가볍고 빠르며 생태계가 성숙해짐에 따라 보편화되고 있다는 점을 지적하는 것이었습니다.

인터페이스를 설명하기 위한 공식 사양은 IMO 가치가 거의 없습니다. 문서화 및 일부 방법을 사용할 수 있는지 확인하는 것에 불과합니다. 후자는 인터페이스의 일부이지만 다른 부분은 이러한 메서드에 의해 구현된 의미 체계입니다 (예: A 가 배열인 경우 axes(A)getindex 유효한 좌표 범위를 제공합니다

그러나 내가 보고 싶은 것은

  1. 점점 더 많은 인터페이스에 대한 문서 (docstring에서),

  2. 새로 정의된 유형에 대한 성숙한 인터페이스에 대한 명백한 오류를 포착하는 테스트 스위트 (예: eltype(::Type{T}) 아닌 eltype(::T) 구현하는 많은 T <: AbstractArray eltype(::Type{T}) .

@tpapp 이제 이해가 됩니다. 감사합니다.

@StefanKarpinski 나는 잘 이해하지 못합니다. 트레잇은 명목 타입이 아니지만(맞나?) 그래도 디스패치에 사용할 수 있습니다.

내 요점은 기본적으로 @tknopp@mauro3가 만든 것입니다. https://discourse.julialang.org/t/why-does-julia-not-support-multiple-traits/5278/43?u=datnamer

특성과 추상 타이핑을 가짐으로써 매우 유사한 두 가지 개념을 갖게 됨으로써 추가적인 복잡성과 혼란이 발생합니다.

사람들이 원하는 것처럼 보이는 것은 간접적인 계층을 피하는 것이지만 그것은 단지 편의일 뿐 필수 사항이 아닌 것처럼 보입니다.

유형 매개변수를 사용하여 결합 및 교차와 같은 항목으로 그룹화하여 특성 계층 구조의 섹션을 강력하게 전달할 수 있습니까? 해보지는 않았지만 언어 지원이 필요한 것 같습니다. 유형 도메인의 IE 표현 문제.

편집: 문제는 여기서 사용되는 인터페이스와 특성의 병합이라고 생각합니다.

이것을 여기에 게시하는 것만으로도 재미있습니다. Concepts가 확실히 승인되었고 C++20의 일부가 될 것 같습니다. 흥미로운 것들!

https://herbsutter.com/2019/02/23/trip-report-winter-iso-c-standards-meeting-kona/
https://en.cppreference.com/w/cpp/language/constraints

나는 특성이 이 문제를 해결하는 정말 좋은 방법이며 신성한 특성은 확실히 먼 길을 왔다고 생각합니다. 하지만 Julia에게 정말 필요한 것은 특성에 속하는 기능을 그룹화하는 방법이라고 생각합니다. 이것은 문서화상의 이유로 유용할 뿐만 아니라 코드의 가독성에도 유용합니다. 내가 지금까지 본 것에서 나는 Rust에서와 같은 특성 구문 이 갈 길이라고 생각합니다.

저는 이것이 매우 중요하다고 생각하며 가장 중요한 사용 사례는 반복자를 인덱싱하는 것입니다. 다음은 작동할 수 있는 구문 유형에 대한 제안입니다. 이미 제안된 경우 사과드립니다(긴 스레드...).

import Base: Generator
<strong i="6">@require</strong> getindex(AbstractArray, Vararg{Int})
function getindex(container::Generator, index...)
    iterator = container.iter
    if <strong i="7">@works</strong> getindex(iterator, index...)
        container.f(getindex(iterator, index...))
    else
        <strong i="8">@interfaceerror</strong> getindex(iterator, index...)
    end
end
이 페이지가 도움이 되었나요?
0 / 5 - 0 등급