ios개발/개념 정리

<스위프트> Custom Operator, Generic, inout parameter

studying develop 2020. 4. 9. 16:20

함수형 언어란??

 

검색해서 텍스트를 찾는데, 자꾸 스위프트 언어 자체에 대한 것만 나와서 유트브에 functional programming + swift로 검색해 보다 아래 유트브를 보았다.

 

[https://www.youtube.com/watch?v=estNbh2TF3E]

 

이 영상 전에, 단순히 functional pl에 대해 찾아 보니, 음 단계 별로 넘길 수 있다는 장점? 그것이 위주로 나왔는데, 용어와 특징을 다시 찾아보자. 

 

그래서 해당 영상에서 본 오퍼레이터 생성이 흥미로워 공식 문서에서 다시 찾아 봤다.

9분

 


 

[https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html]

 

operator의 infix, prefix,postfix 개념이 다시 생각이 났고, associativity에 대해서도 알아야 operator를 만들어 사용할 수 있겠다.

 

아래는 prefix, infix operator 사용법이다.

extension Vector2D {
    static prefix func - (vector: Vector2D) -> Vector2D {
        return Vector2D(x: -vector.x, y: -vector.y)
    }
}

let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative is a Vector2D instance with values of (-3.0, -4.0)
let alsoPositive = -negative
// alsoPositive is a Vector2D instance with values of (3.0, 4.0)



extension Vector2D {
    static func += (left: inout Vector2D, right: Vector2D) {
        left = left + right
    }
}


var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
// original now has values of (4.0, 6.0)

 

 

Equivalence Operators

By default, custom classes and structures don’t have an implementation of the equivalence operators, known as the equal to operator (==) and not equal to operator (!=). You usually implement the == operator, and use the standard library’s default implementation of the != operator that negates the result of the == operator. There are two ways to implement the == operator: You can implement it yourself, or for many types, you can ask Swift to synthesize an implementation for you. In both cases, you add conformance to the standard library’s Equatable protocol.

 

equivalence operator를 예시로 들면서 , 우리가 == 오퍼레이터를 구현하는 2가지 방법이 있다 한다. 첫째 우리 스스로 만들거나, 이 경우 많은 타입들에 대해서 만들어야 하는듯!  또는 Swift에 우리를 위해 구현을 합성 해달라할 수 있따..? 어쨋든 두 경우 모두, 표준 라이브러리의 Equatable 프로토콜에 순응시켜야 한다.

 

extension Vector2D: Equatable {
    static func == (left: Vector2D, right: Vector2D) -> Bool {
        return (left.x == right.x) && (left.y == right.y)
    }
}

extension Vector2D: Equatable {
    static func == (left: Vector2D, right: Vector2D) -> Bool {
        return (left.x == right.x) && (left.y == right.y)
    }
}

In many simple cases, you can ask Swift to provide synthesized implementations of the equivalence operators for you. Swift provides synthesized implementations for the following kinds of custom types:

 

대부분의 경우들에, 스위프트에 나를 위해 equivalence 오퍼레이터들에 대한 합성된 구현들을 제공해달라 할 수 있다. 스위프트는 아래 3개와 같은 합성된 구현들을 제공한다. 

  • Structures that have only stored properties that conform to the Equatable protocol
  • 오직 Equatable 프로토콜에 순응하는 저장된 프로퍼티들만 갖는 구조체들
  •  
  • Enumerations that have only associated types that conform to the Equatable protocol
  • Equatable 프로토콜에 순응하는 연관된 타입만 갖는 Enumeration들 (associated type이 뭔지 모르겠다.... 음 enum에 나오는 단어인가)
  •  
  • Enumerations that have no associated types
  • 연관 타입이 없는 이넘들

 

To receive a synthesized implementation of ==, declare Equatable conformance in the file that contains the original declaration, without implementing an == operator yourself.

==의 합성된 (구현되어 있다는 뜻인듯) 구현을 받기 위해서는, 원본 선언을 포함하는 파일 안에서 equatable 순응을 선언해야 한다. == 오퍼레이터를 내가 구현하지 말고.

 

The example below defines a Vector3D structure for a three-dimensional position vector (x, y, z), similar to the Vector2D structure. Because the x, y, and z properties are all of an Equatable type, Vector3D receives synthesized implementations of the equivalence operators.

~~. xyz 프로퍼티들이 모두 equatable type이므로, 벡터3는 equivalence 오퍼레이터들의 synthesized 구현들을 받는다.

 

struct Vector3D: Equatable {
    var x = 0.0, y = 0.0, z = 0.0
}

let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
if twoThreeFour == anotherTwoThreeFour {
    print("These two vectors are also equivalent.")
}
// Prints "These two vectors are also equivalent."

음 그니까 아래 타입이 해당 프로토콜이 다 순응되어 있으면 구조체나 이넘에 대해 순응할 필요 없다는 의미인듯.

 

 


음 이제 나오는게 내가 찾던거다. 커스텀 오퍼레이터

Custom Operators

You can declare and implement your own custom operators in addition to the standard operators provided by Swift. For a list of characters that can be used to define custom operators, see Operators.

링크 따라 가면 언어의 lexical 구조에 대해 알려 준다. 한번 읽어보자.... 컴파일러때 렉시컬 구조 배웠는데 잘 기억이 안남 다시보자.

 

New operators are declared at a global level using the operator keyword, and are marked with the prefix, infix or postfix modifiers:

새로운 연산자는 글로벌 레벨로, 오퍼레이터 키워드를 사용해 선언 되있다, 그리고 prefix,infix,postfix같은 modifier(???)로 표시되었다.

prefix operator +++

modifier 는 Access modifiers (or access specifiers) are keywords in object-oriented languages that set the accessibility of classes, methods, and other members. Access modifiers are a specific part of programming language syntax used to facilitate the encapsulation of components. 이다

 

extension Vector2D {
    static prefix func +++ (vector: inout Vector2D) -> Vector2D {
        vector += vector
        return vector
    }
}

var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
let afterDoubling = +++toBeDoubled
// toBeDoubled now has values of (2.0, 8.0)
// afterDoubling also has values of (2.0, 8.0)

Precedence for Custom Infix Operators

커스텀 infix 오퍼레이터 우선순위에 대한 것이다.

Operators Precedence in C. Advertisements. Operator precedence determines the grouping of terms in an expression and decides how an expression is evaluated. 우선 순위 음 연산 순서가 더 정확할듯? 

 

Custom infix operators each belong to a precedence group. A precedence group specifies an operator’s precedence relative to other infix operators, as well as the operator’s associativity. See Precedence and Associativity for an explanation of how these characteristics affect an infix operator’s interaction with other infix operators.

커스텀 인픽스 연산자들 각각은 프레시던스 그룹에 속한다. 프레시던스 그룹은 연산자의 우선순위를 다른 인픽스 연산자들과 상대적으로 비교해 특정짓는다, 연산자들의 어소시에이티비티와 함게 정한다. 링크를 누르면 precedence와 associativity에 대해 잘 알 수 있을거 같다.

 

A custom infix operator that is not explicitly placed into a precedence group is given a default precedence group with a precedence immediately higher than the precedence of the ternary conditional operator.

어느 프레시던스 그룹인지 명시적으로 표시되지 않은 커스텀 인픽스 연산자에게 삼항 조건 연산자의 우선순위보다 높은 초기화 프레시던스 그룹을 부여한다.

 

The following example defines a new custom infix operator called +-, which belongs to the precedence group AdditionPrecedence:

infix operator +-: AdditionPrecedence
extension Vector2D {
    static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y - right.y)
    }
}
let firstVector = Vector2D(x: 1.0, y: 2.0)
let secondVector = Vector2D(x: 3.0, y: 4.0)
let plusMinusVector = firstVector +- secondVector
// plusMinusVector is a Vector2D instance with values of (4.0, -2.0)

음 연산자를 선언하고 프레시던스를 부여할 수 있다는건 첨 본듯

 

This operator adds together the x values of two vectors, and subtracts the y value of the second vector from the first. Because it is in essence an “additive” operator, it has been given the same precedence group as additive infix operators such as + and -.

~~. 연산자가 요약하자면 "더하기" 연산자이므로, 연산자는 +,-연산자 같은 인픽스 연산자들과 동일한 우선순위 그룹을 준다.

 

For information about the operators provided by the Swift standard library, including a complete list of the operator precedence groups and associativity settings, see Operator Declarations. For more information about precedence groups and to see the syntax for defining your own operators and precedence groups, see Operator Declaration.

첫 링크를 보면 스위프트가 제공하는 연산자들에 대해 알 수 있다. 우선순위 그룹들 그리고 관련 문법은 두번째 링크 보면 알 수 있다.


[https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID380]

 

infix operator operator name: precedence group

제너릭 타입에 안익숙한데, 꼭 써보자. 

 

Generics

Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.

 

~~ . 우리는 복제가 없고 의지를 명확하고 추상적인 방법으로 코드를 작성할 수 있다.

 

Generics are one of the most powerful features of Swift, and much of the Swift standard library is built with generic code. In fact, you’ve been using generics throughout the Language Guide, even if you didn’t realize it. For example, Swift’s Array and Dictionary types are both generic collections. You can create an array that holds Int values, or an array that holds String values, or indeed an array for any other type that can be created in Swift. Similarly, you can create a dictionary to store values of any specified type, and there are no limitations on what that type can be.

 

제너릭은 스위프트에서 가장 강력한 특징중에 하나 입니다, 대부분의 스위프트 표준 라이브러리들은 제너릭 코드로 작성되었습니다. 사실, 우리는 언어 가이드 전반에 걸쳐 제너릭들을 사용하고 있었습니다. 예를 들면 스위프트 배열과 사전은 모두 제너릭 콜렉션들 입니다. 정수를 갖는 배열을 생성할 수 있습니다, 또는 문자열 값들을 갖는 배열이나, 또는 다른 타입에 대한 배열을 생성할 수 있습니다. 유사하게, 사전도 됩니다. 생성시 타입에 제한이 없습니다.

 

The Problem That Generics Solve

Here’s a standard, nongeneric function called swapTwoInts(_:_:), which swaps two Int values:

 

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

 


 

음 일단 인아웃 변수가 뭔지 보자

In-Out Parameters

Function parameters are constants by default. Trying to change the value of a function parameter from within the body of that function results in a compile-time error. This means that you can’t change the value of a parameter by mistake. If you want a function to modify a parameter’s value, and you want those changes to persist after the function call has ended, define that parameter as an in-out parameter instead.

 

함수 파라미터들은 초기값이 상수입니다. 함수 파라미터를 함수 내부에서 바꾸려는 노력은 결국 컴파일 타임 에러로 끝납니다....? 이 의미는 우리는 파라미터의 값을 실수로 바꿀수는 없다는 얘기입니다. 만약 함수가 파라미터의 값을 바꾸길 원하고 함수 호출이 종료된 후에도 지속되기를 원한다면, 파라미터를 인아웃 파라미터로 대신 정의하십쇼..

 

뭔소린지 모르겠다. 함수 파라미터를 바꾼다는게 무슨 말이지??

 

You write an in-out parameter by placing the inout keyword right before a parameter’s type. An in-out parameter has a value that is passed in to the function, is modified by the function, and is passed back out of the function to replace the original value. For a detailed discussion of the behavior of in-out parameters and associated compiler optimizations, see In-Out Parameters.

 

우리는 인아웃 파라미터를 파라미터 타입 앞에 인아웃 키워드를 위치 시킴으로서 작성합니다. 인아웃 파라미터는 함수로 넘어가는 값을 갖습니다, 이 인아웃 파라미터는 함수에 의해 변경됩니다, 그리고 다시 함수로 돌아가 원래 값을 대체합니다. 자세한 논의를 위해 인아웃 파라미터들과 연관된 컴파일러 최적화를 위해, 링크를 보십쇼.

 

음 무슨 말인지 모르겠어서 난 봐야될거 같다.

 

You can only pass a variable as the argument for an in-out parameter. You cannot pass a constant or a literal value as the argument, because constants and literals cannot be modified. You place an ampersand (&) directly before a variable’s name when you pass it as an argument to an in-out parameter, to indicate that it can be modified by the function.

 

우리는 오직 변수를 인아웃 파라미터의 인자로만 넘길 수 있습니다. 우리는 상수나 리터럴 값을 인자로 넘길 수 없습니다, 왜냐면 상수들과 리터럴 값들은 변경할 수 없기 때문입니다. 우리는 변수의 이름 &을 붙임으로서 인자를 인아웃 파라미터로 넘긴다는 의미입니다, 그럼으로서 넘겨진 값이 함수에 의해 수정될 수 있음을 나타냅니다.

 

Here’s an example of a function called swapTwoInts(_:_:), which has two in-out integer parameters called a and b:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

The swapTwoInts(_:_:) function simply swaps the value of b into a, and the value of a into b. The function performs this swap by storing the value of a in a temporary constant called temporaryA, assigning the value of b to a, and then assigning temporaryA to b.

 

스왑투인트 함수는 단순히 a,b를 스왑한다. 함수는 temporaryA에 값을 일시적으로 저장함으로서 수행한다,~~

 

You can call the swapTwoInts(_:_:) function with two variables of type Int to swap their values. Note that the names of someInt and anotherInt are prefixed with an ampersand(&) when they are passed to he swapTwoInts(_:_:) function:

 

우리는 스왑투인트 함수를 두 변수들을 스왑하기 위해 인트 변수로 스왑한다. 섬인트와 어나더인트 이름들을 스왑투인트 함수로 넘길때 앰퍼샌드로 프리픽스 한다. 

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"

The example above shows that the original values of someInt and anotherInt are modified by the swapTwoInts(_:_:) function, even though they were originally defined outside of the function.

 

이 예시는 섬인트와 어나더인트들을 스왑투인트 함수로 원본 값들을 수정한다, 비록 원본 값은 함수 밖에서 정의되었지만.

 

NOTE

In-out parameters are not the same as returning a value from a function. The swapTwoInts example above does not define a return type or return a value, but it still modifies the values of someInt and anotherInt. In-out parameters are an alternative way for a function to have an effect outside of the scope of its function body.

 

인아웃 파라미터들은 함수에서 값을 반환하는 것과 동일한게 아니다. 스왑투인트 예시는 리턴 타입또는 리턴 값을 정의하지 않는다, 하지만 섬인트와 어나더인트의 값들을 수정한다. 인아웃 파라미터들은 함수의 스코프 밖에 영향을 끼치는 대안 중에 한개 이다.!!@#!@#

 


 

리터럴과 상수에대해 정확히 알고 싶어 다음을 찾음.

 

상수는 변하지 않는 변수고, 리터럴은 변하지 않는 값인거 같다.....

 

상수(Constant)란?


 

  먼저 상수와 리터럴 둘 다, 변하지 않는 값(데이터)를 의미한다. 


코드적으로 말하자면, 상수는 변하지 않는 변수를 뜻한다.

 

상수는 숫자만 넣어야 한다고 오해하는 사람들이 많은데,

 

앞서 말했듯이, 상수는 변하지 않는 변수를 뜻하는 것이다.

 

즉 상수에 넣는 데이터는 숫자가 올 수 도 있지만,

 

클래스나 구조체 같이 기본형에서 파생된 객체나 유도형같은 데이터를 넣을 수 있다. 

 

상수는 데이터가 변하지 않아야 한다고 했다. 그래서 

 

참조변수를 상수로 지정 할 때, 참조변수에 넣은 인스턴스 안의 데이터 까지도 변하지 않는 줄 착각 할 수 있지만,

 

참조변수가 상수(참조변수 메모리의 주소값이 변하지 않는다라는 의미)지, 

 

그 주소가 가리키는 데이터들까지 상수라는 의미가 아니다.

 

프로그래밍에서 상수를 쓸때는 C,C++,C#은 const , Java는 final 제어자를 쓴다.

 

Java언어로 예를 들어보자.

 

즉 Test라는 클래스를 만들었다면, 

 

final Test t1 = new Test();

 

t1 = new Test();

 

는 불가 하지만,

 

t1.num = 10;

 

이렇게 클래스 안의 데이터를 변경해도 상관이 없다는 의미이다.

 

 

리터럴(Literal)이란?


 

  리터럴은 데이터 그 자체를 뜻 한다. 

 

변수에 넣는 변하지 않는 데이터를 의미하는 것이다.

 

아래의 예제를 보자.

 

int a = 1;

 

int 앞에 final를 붙일 시 , a는 상수가 된다. 여기서의 리터럴은 1이다.

 

즉, 1과 같이 변하지 않는 데이터(boolean, char, double, long, int, etc...)를 리터럴(literal)이라고 부른다.

 

그렇다면 인스턴스(클래스 데이터)가 리터럴이 될 수 있을까?

 

답은 아니오다. 만약 인스턴스안에 있는 값들을 변경하지 않는다면 모를까,

 

보통의 인스턴스는 동적으로 사용하기 위해 작성되므로.

 

리터럴이 될 수가 없다. 왜냐하면 값이 언제 바뀔지 모르는 것이기 때문이다.

 

하지만 프로그래밍 에서 객체 리터럴이란 표현을 들어본적이 있을 것이다.

 

데이터가 변하지 않도록 설계를 한 클래스를 불변 클래스라 칭한다.(immutable class)

 

해당 클래스는 한번 생성하면 객체 안의 데이터가 변하지 않는다. 변할 상황이면 새로운 객체를 만들어준다.

 

자바의 String, Color 같은 클래스가 이와 같은 예이다. 

 

따라서 우리는 "abc" 와 같은 문자열을 자바에서는 '객체 리터럴' 짧게는 '리터럴' 이라고 표현 하는것이다.

 

 

 

여기까지가 준비한 상수와 리터럴의 내용이다. 

 

정리하자면 상수는 변하지 않는 변수를 의미하며(메모리 위치) 메모리 값을 변경할 수 없다.

 

리터럴은 변수의 값이 변하지 않는 데이터(메모리 위치안의 값)를 의미한다. 

 

보통은 기본형의 데이터를 의미하지만, 특정 객체(Immutable class , VO class)에 한에서는 리터럴이 될 수 있다.



출처: https://mommoo.tistory.com/14 [개발자로 홀로 서기]

 

 


In-Out Parameters

In-out parameters are passed as follows:

  1. When the function is called, the value of the argument is copied.
  2. In the body of the function, the copy is modified.
  3. When the function returns, the copy’s value is assigned to the original argument.

인아웃 파라미터들은 다음 순으로 넘겨집니다.

1. 함수가 호출되면 인자 값을 복사합니다.

2. 함수 내부에서 , 복사본이 수정됩니다.

3. 함수가 반환되면, 복사본의 값이 원래 인자에 반환됩니다.

 

음 그니까 넘겨준 값이 함수 내부에서 복사되 수정되고 복사본이 원래 인자로 반환되는거 같은데..... 이게 pass by reference랑 뭐가 다른거지 ?ㅋ?ㅋㅋ?? 결과적으로는 비슷한거 같은뎅.... 아 콜바이 리절트네, 근데 아직 언제 뭘 쓰고 그건 잘 모르겠따.

 

This behavior is known as copy-in copy-out or call by value result. For example, when a computed property or a property with observers is passed as an in-out parameter, its getter is called as part of the function call and its setter is called as part of the function return.

 

이 행동은 카피인 카피아웃 또는 call by value result라 부릅니다. 예를 들어,  연산된 프로퍼티나 옵저버를 갖는 프로퍼티를 인아웃 파라미터로 넘기면, 넘긴 프로퍼티의 getter가 함수 호출과 함께 호출되고 setter가 함수가 리턴되며 호출됩니다.

 

As an optimization, when the argument is a value stored at a physical address in memory, the same memory location is used both inside and outside the function body. The optimized behavior is known as call by reference; it satisfies all of the requirements of the copy-in copy-out model while removing the overhead of copying. Write your code using the model given by copy-in copy-out, without depending on the call-by-reference optimization, so that it behaves correctly with or without the optimization.

 

최적화로서, 인자를 물리 메모리의 주소레 저장하면, 동일한 메모리 위치가 함수 안과 밖 모두에서 사용된다. 최적화된 행동은 call by reference이다; 콜바이 레퍼런스는 카피인 카피 아웃 모델의 모든 요구사항들을 만족한다, 동시에 복사에 대한 오버헤드를 제거한다 !!! 우리가 콜바이 레퍼런스 없이 카피인 카피아웃으로 코드를 작성해라, 그러면 코드는 최적화가 있던 없던 동일하게 작동할 것이다......

??여기서 최적화라는게 뭐지??

 

Within a function, don’t access a value that was passed as an in-out argument, even if the original value is available in the current scope. Accessing the original is a simultaneous access of the value, which violates Swift’s memory exclusivity(독점) guarantee. For the same reason, you can’t pass the same value to multiple in-out parameters.

 

함수 내부에서, 인아웃 인자로 넘겨진 값에 접근하지 말아라, 비록 원래 값이 현재 스코프 내에서 사용 가능할 지라도. 원본을 접근하는 것은 값에 대한 동시적인 접근이다. 원본에의 접근은 스위프트 메모리 독점 보장을 위반합니다. 동일한 이유로, 우리는 동일한 값을 복수의 인아웃 파라미터들로 넘길 수 없습니다.

 

For more information about memory safety and memory exclusivity, see Memory Safety.

A closure or nested function that captures an in-out parameter must be nonescaping. If you need to capture an in-out parameter without mutating it or to observe changes made by other code, use a capture list to explicitly capture the parameter immutably(불변 객체).

 

메모리 세이프트와 메모리 익스클루시비티에 대한 정보는, 링크에서 참조.

인아웃 파라미터를 capture하는 클로저나 네스티드된 함수는 반드시 nonescaping이여야 합니다. 만약 우리가 인아웃 파라미터를 뮤테이팅 없이 캡쳐해야 하거나 또는 다른 코드에 의한 변화를 관찰해야 한다면, 캡쳐 리스트를 사용해 명시적으로 파라미터를 이뮤터블하게 캡쳐하십쇼.........

 

일단 이 링크도 좀 봐야 될거같고, mutable ,immutable에 대해서도 공부해야 할거 같다.

 

func someFunction(a: inout Int) -> () -> Int {
    return { [a] in return a + 1 }
}

 

If you need to capture and mutate an in-out parameter, use an explicit local copy, such as in multithreaded code that ensures all mutation has finished before the function returns.

 

만약 인아웃 파라미터를 캡쳐후 뮤테이트 해야 한다면, 명시적인 지역 복사본을 사용해라, 예를 들면 함수가 리턴되기 전에 모든 변이가 완료됨을 보장하는 멀티 스레드된 코드처럼.........

 

func multithreadedFunction(queue: DispatchQueue, x: inout Int) {
    // Make a local copy and manually copy it back.
    var localX = x
    defer { x = localX }

    // Operate on localX asynchronously, then wait before returning.
    queue.async { someMutatingOperation(&localX) }
    queue.sync {}
}

음 코드가 뭘 나타내는지 모르겠다......1@#!@#

 

For more discussion and examples of in-out parameters, see In-Out Parameters.

 

이 링크도 좀 보자.......

 


[https://docs.swift.org/swift-book/LanguageGuide/MemorySafety.html]