Development record of developer who study hard everyday.

레이블이 Kotlin인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Kotlin인 게시물을 표시합니다. 모든 게시물 표시
, ,

How to separate string data by sentences

How to separate string data by sentences

안드로이드 블로그

When I am developing android app using Kotlin, I had to separate string data by one sentence.

There might be so many solutions, but I want short code using regular expression.

My solution is

1
2
3
4
5
6
7
8
9
10
fun main() {
    val text = "감오리는 사막이나 농촌지역에서 주로 발견되며, 땅속에 다닐 수 있는 능력이 있어 땅속에서 먹이를 찾기 위해 파묻기를 합니다. 감오리는 매우 활발한 동물로 매일 새로운 먹이를 찾으며 유혈자로 알려져 있습니다. 감오리는 오래 살아 가는 동물이며, 50년에서 70년까지 살 수 있습니다. 감오리의 수명은 종류에 따라 다르지만, 평균적으로 10년 정도입니다. 감오리는 인간과 친밀한 관계를 맺을 수 있는 동물로, 그들은 상호 의존적인 관계에서 존재합니다."
 
    val pattern = Regex("[^.!?]+[.!?]")        
    val matches = pattern.findAll(text)
 
    val sentences = matches.map { it.value.trim() }.toList()
 
    println(sentences)
}
cs


Share:
Read More
, , , ,

코틀린(Kotlin) 제네릭(Generic) 파헤치기

 코틀린(Kotlin) 제네릭(Generic) 파헤치기

안드로이드 블로그

코틀린 공식문서를 번역하면서 공부 중이다...

오늘은 Generic에 대해서 공부하려고한다.


1. Generics : in, out, where

Kotlin에서 클래스들은 JAVA에서처럼 타입 파라미터를 가질 수 있다.


class Box<T>(t: T) {
var value = t
}


Box 객체를 만들기위해서 아래처럼 타입 매개변수를 부여하자.

val box: Box<Int> = Box<Int>(1)


이때, 파라미터의 타입이 예측될 수 있어서 타입을 생략할 수 있다.

val box: Box = Box(1)


2. 변성(Variance)

자바에서 가장 혼란스러운 부분이 wildcard(와일드카드) 타입이다.

코틀린은 wildcard가 존재하지 않는다.

대신에, 코틀린은 declaration-site variance와 type projection이 존재한다.


왜 자바는 wildcard가 필요할까?

Effective JAVA 3rd Edition을 보면, API의 유연성을 높이기위해 wildcard를 사용하라고한다.

Java에서 제네릭 타입은 invariant(불변)이다.

예를 들어, List가 invariant(불변)이 아니라면, 자바의 array보다 나을게 없다.

왜냐하면 아래 코드가 컴파일 되더라도 런타임에서 exception을 던질 것이기 때문이다.

// Java
List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // !!! A compile-time error here saves us from a runtime exception later.
objs.add(1); // Put an Integer into a list of Strings
String s = strs.get(0); // !!! ClassCastException: Cannot cast Integer to String

Java는 런타임시의 안전성을 위해 위와 같은 코드진행을 못하게한다.

하지만 이것이 암시하는 바가 있다.

예를 들어, Collection interface의 addAll()을 생각해보자.

이 메소드의 매개변수가 무엇일까?

// Java
interface Collection<E> ... {
void addAll(Collection<E> items);
}


하지만 이때, 아래와 같이 할 수 없다.

// Java
void copyAll(Collection<Object> to, Collection<String> from) {
to.addAll(from);
// !!! Would not compile with the naive declaration of addAll:
// Collection<String> is not a subtype of Collection<Object>
}


따라서, addAll() 메소드의 실제 매개변수는 아래와 같다.

// Java
interface Collection<E> ... {
void addAll(Collection<? extends E> items);
}


? extends E로 표현되는 wildcard 타입은 이 메소드가 E 뿐만아니라 E객체의 collection 또는 E를 상속하는 타입까지 받을 수 있다는 것을 말해준다.

다시 말해, Collection에서 E 타입을 안전하게 읽을 수 있다는 것이다.

하지만, 어떤 객체가 E의 하위항목인지 모르기때문에 값을 write할 수 는 없다.

이러한 제한때문에 Collection<String>이 Collection<? extends Object>의 하위항목이였으면 하는 생각이 든다.

즉, extend로 이루어진 wildcard가 covariant(공변성)을 이루었으면 한다.


공변성의 핵심은 간단하다.

collection에서 값을 가져왔으면, Object 타입으로 읽어와서  String 타입의 Collection을 사용하라.

반대로, collection에 값을 넣을 수 있다면, Object 타입의 collection으로 받아서 String 타입의 값을 넣어줘라. 

예를 들어, Java에는 List<? super String>이라는 List<Object>의 supertype이 있다.


후자를 반변성이라고 한다.

당신은 오직 List<? super String>의 매개변수인 String 타입의 값을 취하는 메소드를 호출할 수 있다.

ex) add(String), set(int, String)

만약 List<T>에서 T 타입을 리턴하는 무언가를 호출한다면, 그것은 String 타입이 아니라 Object 타입을 받을 것이다.


Joshua Bloch는 오로직 읽어들이는 객체에 Producers라는 이름을 붙였고

오로지 쓰는(write) 객체에 Consumers라는 이름을 붙였다

"For maximum flexibility, use wildcard types on input parameters that represent producers or consumers", and proposes the following mnemonic:

PECS stands for Producer-Extends, Consumer-Super.


Declaration-site variance(선언부 변성)

Source<T>라는 제네릭 인터페이스가 있다고하자.

이 인터페이스는 T를 매개변수로 취하는 어떤 메소드도 갖지 않는다.

오로지 T를 리턴하는 메소드만 존재한다.

// Java
interface Source<T> {
T nextT();
}


이때, Source<Object> 타입의 변수에 Source<String>타입의 객체를 저장하기에 완벽하다.

여기에 consumer는 없다.

하지만 자바는 이것을 알지못하고 하지못하게한다.


// Java
void demo(Source<String> strs) {
Source<Object> objects = strs; // !!! Not allowed in Java
// ...
}


이를 해결하기 위해서, 우리는 Source<? extends Object>를 선언해야만한다.

이렇게하는 것은 의미가 없다.

왜냐하면  Source<? extends Object>를 선언하기 이전에 모두 같은 메소드를 호출할 수 있기 때문이다.

그래서 더 복잡한 타입에의해 추가된 값은 없다.

하지만 컴파일러는 그것을 알지 못한다.(= Source<Object>로 Source<String>을 받을 수 없다.)


Kotlin에서는 컴파일러에게 이렇게 설명한다.

이게 바로 declaration-site variance 이다.

매개변수 Source<T>는 오로지 Source<T>의 하위객체로 리턴된(produced된) 것이고 consume은 절대 하지 않는다는 것을 표현할 수 있다.

"out" 한정자를 사용하자.

interface Source<out T> {
fun nextT(): T
}

fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // This is OK, since T is an out-parameter
// ...
}


일반적인 규칙은 이렇다.

C<out T> => it may occur only in the out-position in the members of C, but in return C<Base> can safely be a supertype of C<Derived>

[ 😥너무 어렵다;;

C클래스의 멤버에서 out 포지션에서만 발생한다. 하지만 그 결과, C<Base>는 안전하게 C<Derived>의 supertype이 될 수 있다...??

out이 Java에서 <? extends T>와 같으니까 ?에 어떤값이 와도 T로 받을 수 있다 라는 말 아닐까? ]

다른 말로, C 클래스는 파라미터 T에 covariant(공변성)하다. 또는 T는 covariant한 타입 파라미터다.

C 클래스를 T의 producer로 생각할 수 있다. 물론 consumer는 아니다.


Kotlin은 추가적으로 variance annotation을 제공한다. 바로 in이다.

in은 타입 파라미터를 반변성으로 만든다.

타입 파라미터는 오로지 consume되고 절대 produce 될 수 없다.

좋은 예가 바로 Comparable interface이다.

interface Comparable<in T> {
operator fun compareTo(other: T): Int
}

fun demo(x: Comparable<Number>) {
x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number
// Thus, you can assign x to a variable of type Comparable<Double>
val y: Comparable<Double> = x // OK!
}


3. 타입 프로젝션(Type projections)

타입 프로젝션은 Use-site variance이다

타입 파라미터 T를 out으로 선언하는 것은 쉽고 타입 파라미터를 사용하는 부분에서 여러 문제를 피할 수 있다.

하지만 몇몇 클래스들은 오직 T로 리턴하게끔 제한될 수 없다.

class Array<T>(val size: Int) {
operator fun get(index: Int): T { ... }
operator fun set(index: Int, value: T) { ... }
}

☝ 위 클래스는 공변성도 반변성도 없다.

따라서 여러 문제들이 발생된다.


fun copy(from: Array<Any>, to: Array<Any>) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
}

☝ 위 메소드는 한 Array를 다른 Array로 내용을 복사하는 역할을 한다.

한 번 실행해보자


val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
copy(ints, any)
// ^ type is Array<Int> but Array<Any> was expected
Array<T>는 T에 대해서 불변성을 가지고있다.

그리고 Array<Int>와 Array<Any>는 둘다 어떤 객체의 하위타입이 아니다.

copy 메소드는 예를들어 from 배열로 String을 쓰게끔 시도할 수 있고 이럴 때 ClassCastException이 발생할 것이다.

이러한 상황을 막기위해 아래와 같이 수정한다.

fun copy(from: Array<out Any>, to: Array<Any>) { ... }

☝ 이것이 type projection이다.

from array는 단순한 배열이 아니고 제한된(projected) 배열이다.

당신은 오로지 타입 파라미터 T를 반환하는 메소드를 호출할 수 있다.

이 경우에, 오로지 get() 메소드만 부를 수 있다.

이것이 use-site variance이다.

이것은 Java의 Array<? extends Object>와 같다.


또한, in을 가지고 타입을 project할 수 있다.

fun fill(dest: Array<in String>, value: String) { ... }

Array<in String>은 Java의 Array<? super String>과 같다.

이것은 dest 배열에 CharSequence나 Object를 fill()할 수 있다는 뜻이다.


스타 프로젝션(Star-projection)

때때로 타입 매개변수에대해 아는게 없는데 안정적으로 코드를 진행하고 싶을 때가 있다.

그럴 때 제네릭 타입의 projection을 정의하는 방식이 있다.

제네릭 타입의 모든 객체화가 projection의 하위타입이 될 것이다.

이름하여 스타 프로젝션(star-projection)이라 부른다.

- Foo<out T : TUpper>, 즉 T가 TUpper에 공변성이 있을 때, 

Foo<*>은 Foo<out TUpper>과 같다.

따라서 T를 알지 못할 때도 Foo<*>에서 TUpper의 값을 안정적으로 읽을 수 있다.


- Foo<in T>, 즉 T가 반변성일 때,

Foo<*> 는 Foo<in Nothing>과 같다.

따라서 T를 알지 못할 때, Foo<*>에 어떤 값도 쓸 수 없다.


- Foo<T : TUpper>, 즉 타입 파라미터 T가 TUpper일 때, Foo<*>는 값을 읽을 때는 Foo<out TUpper>와 같고 값을 쓸 때는 Foo<in Nothing>과 같다.


제네릭 타입이 여러 타입 파라미터를 가진다면, 각각은 독립적으로 project된다.

예를 들어, interface Function<in T, out U>라면, 스타 프로젝션을 아래와 같이 사용할 수 있다.

- Function<*, String>은 Function<in Nothing, String>이다.

- Function<Int, *>은 Function<Int, out Any?>이다.

- Function<*, *>은 Function<in Nothing, out Any?>이다.


4. 제네릭 함수(Generic functions)

클래스만 타입 파라미터를 가지는 것이 아니다.

함수도 타입 파라미터를 가질 수 있다.

함수에서 타입 파라미터는 함수 이름 바로 앞에 붙는다.

fun <T> singletonList(item: T): List<T> {
// ...
}

fun <T> T.basicToString(): String { // extension function
// ...
}


제네릭 함수를 호출할 때ㅔ, 함수 이름 뒤에 타입 파라미터를 명시해준다.

val l = singletonList<Int>(1)


타입 파라미터가 예측될 때는 생략 가능하다.

val l = singletonList(1)


5. Generic 제한(Generic constraints)

타입 파라미터를 대체할 수 있는 모든 가능성있는 타입의 입력은 generic constraints 에 의해 제한될 지도 모른다.


가장 흔한 제한은 upper bound이다.

이것은 Java의 extends 키워드와 같다.

fun <T : Comparable<T>> sort(list: List<T>) {  ... }

: 이후에 나온 타입이 upper bound이다.

오로지 Comparable<T>의 하위타입만이 T를 대체할 수 있다는 뜻이다.

sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable<Int>
sort(listOf(HashMap<Int, String>())) // Error: HashMap<Int, String> is not a subtype of Comparable<HashMap<Int, String>>


upper bound의 기본값은 Any?이다.

오로지 하나의 upper bound만이 <>에 명시될 수 있다.

만약 하나 이상의 upper bound가 필요하다면 where 구문을 사용해야한다.

fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it.toString() }
}

전달된 T는 where 구문의 모든 조건을 충족해야한다.

위의 코드는 T는 반드시 CharSequence와 Comparable을 구현해야만한다.


6. 타입 제거(Type erasure)

Kotlin이 제네릭 선언을 사용하는 타입 안정성체크는 컴파일 타임에 이루어진다.

런타임시, 제네릭 타입의 객체는 실제 타입 매개변수에 대한 어떤 정보도 갖지않는다.

타입에 대한 정보는 지워진다는 것이다.

예를 들어, Foo<Bar>와 Foo<Baz?>는 Foo<*>으로 지워진다.


제네릭 타입체크와 형변환(Generics type checks and casts)

타입 제거(Type erasure) 때문에 런타임시에 제네릭 타입이 어떤 타입으로 만들어졌는지 확인할 수 있는 방법이 없다.

그리고 컴파일러는 is의 사용을 막는다.

예를들어, ints is List<Int>나 list is T 는 불가능하다.

그러나 star-projected 타입을 사용하여 객체의 타입을 체크할 수 있다.

if (something is List<*>) {
something.forEach { println(it) } // The items are typed as `Any?`
}


이와 비슷하게, 이미 타입이 확인된 객체를 가지고있다면, is 사용도 가능하고 제네릭 타입이 아닌 타입으로 형변환도 가능하다.

아래 예제코드를 살펴보자. <>에 타입이 생략된 것을 주목하라.

fun handleStrings(list: MutableList<String>) {
if (list is ArrayList) {
// `list` is smart-cast to `ArrayList<String>`
}
}

타입 매개변수가 생략된 문법이 형변환에도 사용될 수 있다. (list as ArrayList)


제네릭함수 호출에서 타입 매개변수는 컴파일 타임에 체크된다.

함수 본체 안에서, 타입 매개변수는 타입 체크를 사용할 수 없고 타입 매개변수로의 형변환(foo as T)도 확인될 수 없다.

유일한 예외는 reified 타입 매개변수이다.

reified는 호출부에 인라인된 실제 타입 매개변수를 가진다.

그래서 타입 체크와 형변환이 가능하다.

그러나 위에서 언급한 제한은 여전히 타입 체크나 형변환에 사용된 제네릭 타입의 객체를 적용한다.

예를들어, arg is T 라는 타입체크 상황에서, arg가 제네릭 타입 그 자체라면, 타입 매개변수는 지워진다.

inline fun <reified A, reified B> Pair<*, *>.asPairOf(): Pair<A, B>? {
if (first !is A || second !is B) return null
return first as A to second as B
}

val somePair: Pair<Any?, Any?> = "items" to listOf(1, 2, 3)


val stringToSomething = somePair.asPairOf<String, Any>()
val stringToInt = somePair.asPairOf<String, Int>()
val stringToList = somePair.asPairOf<String, List<*>>()
val stringToStringList = somePair.asPairOf<String, List<String>>() // Compiles but breaks type safety!
// Expand the sample for more details


확인되지않은 형변환(Unchecked cast)

제네릭타입에서 확실한 타입으로의 형변환(foo as List<String>)은 런타임시 확인될 수 없다.

이런 확인되지않은 형변환(Unchecked cast)는 타입 안전성이 추정되는 높은 수준의 프로그램에서 사용 가능하다.

하지만 컴파일러에 의해 직접적으로 이루어지지는 않는다.

아래 코드를 살펴보자.


fun readDictionary(file: File): Map<String, *> = file.inputStream().use {
TODO("Read a mapping of strings to arbitrary elements.")
}

// We saved a map with `Int`s into this file
val intsFile = File("ints.dictionary")

// Warning: Unchecked cast: `Map<String, *>` to `Map<String, Int>`
val intsDictionary: Map<String, Int> = readDictionary(intsFile) as Map<String, Int>

마지막 줄에 경고가 나타난다.

컴파일러는 런타임시에 완전히 타입 체크를 할 수 없고 Map 객체 안의 값이 Int라는 것을 보장할 수 없다.

unchecked cast를 피하기위해서는, 프로그램 구조를 다시 짜야한다.

위의 예제코드처럼, 타입 안전성을 위해서 DictionaryReader<T>와 DictionaryWriter<T> 인터페이스를 사용할 수 있다.

unchecked cast를 호출부에서 implmentation으로 옮긺으로서 합리적인 추상화를 도입할 수 있다.

arg의 타입이 지워진 타입이 아니라면 제네릭 함수에서 reified 타입 매개변수를 사용하는것이 arg as T 같은 형변환을 가능케한다.

@Suppress("UNCHECKED_CAST")를 사용하면 unchecked cast 경고가 무시된다.

inline fun <reified T> List<*>.asListOfType(): List<T>? =
if (all { it is T })
@Suppress("UNCHECKED_CAST")
this as List<T> else
null


7. Underscore 연산자

Underscore(_)는 타입 매개변수를 위해 사용될 수 있다.

다른 타입이 명시적으로 정해져있을 때 타입 매개밴수를 자동으로 추론하기위해 사용한다.

abstract class SomeClass<T> {
abstract fun execute() : T
}

class SomeImplementation : SomeClass<String>() {
override fun execute(): String = "Test"
}

class OtherImplementation : SomeClass<Int>() {
override fun execute(): Int = 42
}

object Runner {
inline fun <reified S: SomeClass<T>, T> run() : T {
return S::class.java.getDeclaredConstructor().newInstance().execute()
}
}

fun main() {
// T is inferred as String because SomeImplementation derives from SomeClass<String>
val s = Runner.run<SomeImplementation, _>()
assert(s == "Test")

// T is inferred as Int because OtherImplementation derives from SomeClass<Int>
val n = Runner.run<OtherImplementation, _>()
assert(n == 42)
}




Share:
Read More