Android – getFragment NullPointerException 문제 해결하기

MaterialNavigationDrawer와 ViewPager (v4 22.2.0)를 사용 할때 (즉 다중 Fragment를 사용했을 때) getFragment 시 NullPointException 문제가 발생합니다.

정확한 이유는 아직 찾지 못했으나, stackoverflow에서 동일한 문제를 겪고 있는 분들이 있더라고요. 일단 증상부터 해결책을 살펴보도록 하겠습니다.

일단 에러 코드는 아래와 같습니다.

java.lang.NullPointerException
at android.support.v4.app.FragmentManagerImpl.getFragment(FragmentManager.java:569)
at android.support.v4.app.FragmentStatePagerAdapter.restoreState(FragmentStatePagerAdapter.java:211)
at android.support.v4.view.ViewPager.onRestoreInstanceState(ViewPager.java:1281)
at android.view.View.dispatchRestoreInstanceState(View.java:12043)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2688)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2694)
...

일단 문제의 원인은 childFragment 이슈 때문에 발생하는데, 아래의 방법은 그렇게 확실하지 않은 방법이라고 하네요. 원인은 states 복구 쪽에서 일어나는 거 같습니다.

그래서 아래의 해결책은 상태값을 저장하지 못하게 되는데요, 이부분을 사용하지 않을거라면 아래의 방법으로 해결하여도 무방할거 같습니다.

아래의 부분을 FragmentStatePagerAdapter 상속한 클래스에 넣어주시면 됩니다.
아래의 코드는 상태 복구부분에서 아무것도 안하는 것을 확인 할 수 있습니다.

    @Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
    //do nothing here! no call to super.restoreState(arg0, arg1);
}

끄읕.

JAVA – 자주하는 실수 10가지

원문

이 글은 자바 개발자들이 자주하는 실수 10가지에 대한 글입니다.

1. Array 객체를 ArrayList로 변환하기

아래와 같이 자주들 이렇게 사용하고는 합니다:

List<String> list = Arrays.asList(arr);

하지만! Arrays.asList()메서드는 Arrays클래스 내부에 있는 private static class 객체를 리턴합니다. java.util.ArrayList class가 아니라는 말이죠.
java.util.Arrays.ArrayList 클래스는 set(), get(), contains() 메서드를 가지며 대신 그외 element를 추가하기 위한 아무 메서드도 지원하지 않습니다. 그래서 리스트의 사이즈는 항상 일정합니다.

그럼 어떻게 사용해야 할까요?

ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(arr));

ArrayList 생성자는 Collection type의 객체를 받을 수 있고, 이는 java.util.Arrays.ArrayList가 됩니다.

2. 만약 Array 객체에서 특정 값을 찾는다면?

개발자들은 아래와 같이 자주 사용하고는 합니다.

Set<String> set = new HashSet<String>(Arrays.asList(arr));
return set.contains(targetValue);

위 코드는 정상적인 작동을 합니다. 하지만 구지 List를 Set으로 변환 할 필요는 없습니다. 이는 추가적인 시간 비용이 들기 때문에 아래와 같이 간단하게 해결 할 수 있습니다.

Arrays.asList(arr).contains(targetValue);

또는

for(String s: arr){
    if(s.equals(targetValue))
        return true;
}
return false;

첫 번째 방법이 두번째 방법보다 읽기가 편하겠죠?

3. 루프 상태에서 List의 특정 객체 지우기

아래의 코드를 돌리다면 어떠한 일들이 벌어질까요? 한번 생각해봅시다.

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
for (int i = 0; i < list.size(); i++) {
    list.remove(i);
}
System.out.println(list);

결과는 처참합니다.

[b, d]

위 코드는 심각한 문제를 가지고 있습니다. 특정 객체를 지우게 되면, 리스트의 사이즈는 줄어들게 되고, 삭제할 타겟의 인덱스 또한 변하게 됩니다. 그래서 만약 루프 안에서 인덱스를 사용하여 여러개의 Element를 지우게 한다면 정상적으로 작동하지 않게 됩니다.

아마 알지도 모르겠지만 대안은 iterator를 사용하면 됩니다. 그리고 foreach문이 있겠죠. 자바에서 foreach문은 iterator처럼 작동하지만 사실 iterator가 아닙니다.

아래의 코드를 보도록 하죠.

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
 
for (String s : list) {
    if (s.equals("a"))
        list.remove(s);
}

위 코드는 ConcurrentModificationException를 발생시킵니다.

하지만 아래의 코드는 정상적으로 작동합니다.

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
    String s = iter.next();
 
    if (s.equals("a")) {
        iter.remove();
    }
}

.next() 메서드는 .remove() 메서드가 호출되기 전에 호출되어야 합니다. foreach문에서 ConcurrentModificationException 문제의 원인은 Compiler.remove() 호출 후에 .next()가 불리도록 만들기 때문입니다.

자세한 내용은 소스코드를 참조해주세요.

4. Hashtable vs HashMap

알고리즘 관례에서, Hashtable은 데이터 구조의 이름이라고 한다고 하네요. 하지만 자바에서는 데이터 구조 이름은 HashMap이라고 합니다.

여기서 중요한 차이점 하나는 Hashtable은 동시성을 가진다는 것과 HashMap는 그렇지 않다는 것입니다.

그래서 Hashtable보다 HashMap의 사용을 많이 합니다.

HashMap vs. TreeMap vs. Hashtable vs. LinkedHashMap
Top 10 questions about Map

5. Collection의 Row Type을 사용해라!?

자바에서, raw type과 unbounded wildcard type은 같이 사용하기 쉽습니다. Set이 raw type이고, Set이 unbounded wildcard type입니다.

파라미터로 raw type의 List객체를 사용하는 코드를 생각해봅시다.

public static void add(List list, Object o){
    list.add(o);
}
public static void main(String[] args){
    List<String> list = new ArrayList<String>();
    add(list, 10);
    String s = list.get(0);
}

이 코드는 아래와 같이 예외를 발생하게 하는데,

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at ...

여기서 **Set,Set,그리고 **Set는 몇가지 차이점이 있습니다.

자세한건 Raw type vs. Unbounded wildcardType Erasure를 참조해주세요.

6. 접근 제어

개발자들은 자주 public 클래스 필드를 사용합니다. 필드에 대한 접근이 다이렉트로 이루어지기 때문에 매우 쉽기는 하지만 클래스 디자인 측면에서는 좋지 않습니다. 접근 제어에 대해서는 적으면 적을 수록 좋은 디자인입니다.

자세한 내용은 아래의 링크를 참조해주세요.
public, default, protected, and private

7. ArrayList vs. LinkedList

ArrayListLinkedList의 차이점을 잘 모르는 개발자들은 주로 ArrayList를 사용하고는 합니다. 그 이유는 매우 친숙하기 때문이죠. 그렇지만, 여기에는 아주 큰 퍼포먼스 차이가 있습니다.

LinkedList의 경우 추가와 삭제가 빈번하게 사용될때 사용되어야 하며, 또한 random access 동작이 많지 않습니다.

좀더 상세한 정보를 얻기 위해서 ArrayList vs. LinkedList을 참조해주세요.

8. Mutable vs. Immutable

Immutable 객체는 단순함, 안전함, 기타 등등의 여러가지의 혜택을 받습니다. 하지만 제한된 값을 떼어낼때, 객체들에 많은 변화가 필요할때 가비지 컬렉션이 자주 일어나게 되는데요, 이럴대는 발란스를 맞춰 선택을 해야합니다. 내가 mutable을 사용할지 아니면 immutable을 사용할지에 대해서 말입니다.

일반적으로, mutable객체는 많은 객체의 생상을 피하기 위해 사용되는데요, 아주 오래된 예로 문자열의 결합을 말할 수 있을 있을거 같습니다. 만약 immutable string을 사용한다면, 아무래도 많은 객체 생성과 그에 합당한 가비지 컬렉션이 일어나겠죠. 이는 시간과 cpu 전력을 낭비하는 일입니다. 이럴때는 간단하면 mutable 객체(e.g. StringBuilder)를 사용하면 됩니다.

String result="";
for(String s: arr){
    result = result + s;
}

mutable객체가 매력적으로 보일때는 이런 상황입니다. 예로, 메서드 호출로 하나의 값이 아닌 여러개의 값을 얻고자 할때, 그리고 정렬이나, 필터링 등이 될수 있습니다. 물론 제대로 사용하지 않으면 많은 비용을 그만큼 지불하게 되겠죠.

자세한 내용은 아래의 링크를 참조해 주세요.
from dasblinkenlight’s answer on stack overflow

Why String is Immutable?

9. Super와 Sub의 생성자

Implicit-super-constructor-is-undefined-for-default-constructor

기본 슈퍼 생성자는 정의되어 있지 않아 에러가 발생한다. 자바에서 만약 생성자를 생성하지 않으면, 컴파일러는 인자를 받지 않는 기본생성자를 기본으로 생성한다.

만약 슈퍼 클래스에 생성자가 정의되어 있다면, 위와 같은 경우 Super(String s), 컴파일러는 파라미터가 없는 생성자를 만들지 않을 것이다.

그 파라미터가 있거나 또는 없는 서브 클래스의 생성자는 파라미터가 없는 슈퍼 생성자를 호출한다.
컴파일러는 서브 클래스에 super() 생성자를 추가할려고 할것이고, 그렇지만 그 슈퍼의 기본 생성자는 정의 되어 있지 않다. 그래서 컴파일러는 에러를 표시할 것이다.

이 문제를 해결하기 위해서는 간단하게 Super()생성자를 생성하는것이다.

public Super(){
    System.out.println("Super");
}

또는 슈퍼 생성자를 지우고 서브 생성자를 생성하는 것이다.

Constructor of Super and Sub

10. “”or 생성자?

문자열은 아래와 같이 두가지 방식으로 생성될 수 있다.

//1. use double quotes
String x = "abc";
//2. use constructor
String y = new String("abc");

어떤 차이점이 있을까?

아래의 코드로 빠르게 확인 할 수 있다.

String a = "abcd";
String b = "abcd";
System.out.println(a == b);  // True
System.out.println(a.equals(b)); // True
 
String c = new String("abcd");
String d = new String("abcd");
System.out.println(c == d);  // False
System.out.println(c.equals(d)); // True

좀 더 자세한 내용을 알기 위해서는 아래의 링크로 확인 할 수 있다.
Create Java String Using ” ” or Constructor?.

내일을 향해
위 목록들은 GitHub, Stack Overflow의 open source project의 분석으로 통한 질문으로, 구글 인기 검색이다. 탑 10에 대한 정확한 평가, 수치는 없지만 공통적으로 많이 언급이 되었던 부분이다.

이상

Android – Robolectric를 활용한 Unit Testing

원문

Robolectirc 설정

RobolectricJUnit 4(단말기 필요)는 JVM환경에서 안드로이드 어플리케이션을 테스트 할 수 있는 프레임웍이다. 좀 더 많은 정보는 여기에서 얻을 수 있다.

테스트 환경

  • Android Studio 1.3.1 (1.2.1.1++)
  • Android Gradle Plugin 1.3.0 (1.2.3+)
  • Gradle 2.3.0 (2.2.1+)

Note. Robolectirc은 Android Studio 1.1에서 구동 될 수 있으나, Robolectric Gradle Plugin과 별도의 설정이 필요하다.

또한 Android Studio 1.1에서의 unit testing은 여기에서 확인 할 수 있다.

App build.gradle

첫번째, app 폴더 안에 build.gradle안에 아래의 내용을 추가해야 한다. 현재 최종 버전은 3.0.

dependencies {
    ...
    testCompile 'org.robolectric:robolectric:3.0'
  }

Android Studio 설정

다은 스탭으로.. Unit Testing을 하기 위해 몇가지 설정 할게 필요한데, 첫 번째로 왼쪽 사이드 메뉴에 있는 Build Variants눌러 준다. Test Artifact를 Unit Tests로 선택해준다.

각 타입 별 테스트를 위해, Android Studio는 아래와 같이 경로들을 사용한다. * Unit Tests => src/test/java * Android Instrumentation Tests => src/androidTest/java

기본으로 되어있는 Test는 Android Instrumentation Tests로 Unit Tests경로로 바꿔 준다. 가장 쉬운 방법으로 rename 이름 변경 기능을 사용하자.

Note. 맥을 사용한다면, 경로 이슈가 있는데 아래와 같이 변경해주면 문제를 해결 할 수 있다.

Go to Run -> Edit Configurations -> Default -> Junit으로 이동하면 working directory를 [code]$MODULE_DIR$[/code]로 변경해준다.

좀 더 많은 자료는 여기에서 확인 할 수 있다.

모든 설정이 완료 되었다.

Robolectric test 코드 작성

  • Project Name : RSample
  • MyApplication
public class MyApplication extends Application {
    public LoginHandler getLoginHandler() {
        return new LoginHandler();
    }
}
  • LoginHandler
public class LoginHandler {
    public void login(String id, String pwd) {
    }
}
  • MainActivityTest
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;

import static java.lang.System.out;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.robolectric.shadows.ShadowLog.stream;

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
    MainActivity activity;
    private LoginHandler loginHandler;
    private MyApplication mockedApplication;

    @Before
    public void setup() throws Exception {
        stream = out;

        activity = Robolectric.setupActivity(MainActivity.class);
        activity = Mockito.spy(activity);

        mockedApplication = mock(MyApplication.class);
        loginHandler = mock(LoginHandler.class);
        when(mockedApplication.getLoginHandler()).thenReturn(loginHandler);
        doReturn(mockedApplication).when(activity).getApplication();
    }

    @Test
    public void validateTextViewContent() throws Exception {
        TextView tvHelloWorld = (TextView) activity.findViewById(R.id.a_main_titleTv);
        assertThat(tvHelloWorld.getText().toString(), is("Hello world!"));
    }

    @Test
    public void login() throws Exception {
        Button loginBtn = (Button) activity.findViewById(R.id.a_main_loginBtn);
        loginBtn.performClick();
    }
}
  • MainActivity
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final EditText idEt = (EditText) findViewById(R.id.a_main_idEt);
        final EditText pwdEt = (EditText) findViewById(R.id.a_main_pwdEt);
        Button loginBtn = (Button) findViewById(R.id.a_main_loginBtn);

        loginBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String id = idEt.getText().toString();
                String pwd = pwdEt.getText().toString();
                login(id, pwd);
            }
        });
    }

    private void login(String id, String pwd) {
        getLoginHander().login(id, pwd);
    }

    private LoginHandler getLoginHander() {
        MyApplication app = (MyApplication) getApplication();
        return app.getLoginHandler();
    }
}
  • activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:paddingLeft="@dimen/activity_horizontal_margin"
              android:paddingRight="@dimen/activity_horizontal_margin"
              android:paddingTop="@dimen/activity_vertical_margin"
              android:paddingBottom="@dimen/activity_vertical_margin"
              android:orientation="vertical"
              tools:context=".MainActivity">

    <EditText
        android:id="@+id/a_main_idEt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Your id"/>

    <EditText
        android:id="@+id/a_main_pwdEt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:password="true"
        android:hint="Your pwd"/>

    <TextView
        android:id="@+id/a_main_titleTv"
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/a_main_loginBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Login"/>
</LinearLayout>

이제 Run MainActivityTest(Shift + F10)를 통해 테스트가 가능하다.

끝.

참조1
참조2

Emacs > 맥을 위한 이맥스 Daemon 설정하기

(참조)[https://gist.github.com/ambethia/304964]

여러가지 스크립트가 포함되어 있는 이맥스를 실핼할 때 반복적인 시간이 소요 되는데 이를 해소 할 수 있는 방법을 소개합니다.

맥에 이맥스가 이미 설치 되어 있다는 가정하에 문서를 작성하였습니다.

이맥스 데몬 어플리케이션 생성하기

맥에서 ‘Script Editor’를 열어 아래의 내용을 입력해주세요.

tell application "Terminal"
do shell script "/Applications/Emacs.app/Contents/MacOS/Emacs --daemon"
endtell

저장 할 때는 File > Export, 그리고 File Format : Application을 선택하여 Applications/ 폴더에 저장해주세요.

이상 이맥스 데몬 어플리케이션을 생성하였습니다.

이제 맥이 실행될 때 이 앱을 실행 시켜주기 위해서 어플리케이션을 등록해 봅시다.

  • System Preferences > Accounts 열기
  • 계정 선택 후 Login Items탭 선택
  • ‘+’버튼을 눌러 Emacs Daemon을 선택
  • hide 체크방식에 대해서는 생각할 필요 없음. (나중에 앱 아이콘을 없앨 예정)

이맥스 클라이언트 어플리케이션 생성하기

다시 한번 Script Editor를 열어 아래의 내용을 넣어주세요.

tell application "Terminal"
do shell script "/Applications/Emacs.app/Contents/MacOS/bin/emacsclient -c"
endtell

저장 할 때는 위와 동일하게 Application타입을 선택해주시고, Emacs Client 이름으로 Applications/폴더에 저장해주세요.

새로 생성한 Emacs Client에 아이콘 적용하기

  • Finder를 열어 Application Folder 선택해주세요.
  • Emacs application선택 후 우측 버튼 > Show Package Contents를 선택해주세요.
  • Contents/Resources 폴더 안에 Emacs.icns를 Emacs Client 폴더안에 있는 Content/resources 폴더에 넣어주세요. 그리고 기존에 있던 applet.icns를 삭제하시고 Emacs.icns파일을 applet.icns파일 이름으로 변경해주세요.

Emacs Client가 동작 되는 동안 Dock에서 Emacs 아이콘을 숨겨보자.

  • Application folder에 있는 Emacs Application (Emacs Client가 아님), Show Package Contents로 열어주세요.
  • Info.plist파일을 열어 Add child를 통해 키 NSUIElement, 값 1을 설정해주세요.

모든 설정이 완료 되었네요.

그럼 즐거운 이맥스 라이프!!

Android – Kakaolink 활용하기

kakaolink를 활용하여 특정 문자열 서드파트 어플리케이션에 전달하고자 한다.

상황 예)
전송자 : 카톡을 활용하여 친구에게 메시지를 보낸다. “사전에서 ‘apple’이라는 걸 검색해봐~”
수신자 : 카톡에서 받은 메시지와 함께 사전 검색 버튼이 생성된다. 버튼을 누르게 되면 사전앱이 실행되고 자동으로 검색되게 된다.

kakalink

위와 같은 상황에서 kakaolink를 적극적으로 활용할 수 있다.

  • 어플리케이션 호출 및 문자열 데이터를 받기 위한 권한 부여

    strings.xml 파일

    // key 선언
    <string name="kakao_app_key">000000000000000000</string>
    <string name="kakao_scheme">kakao000000000000000000</string>
    <string name="kakaolink_host">kakaolink</string>

AndroidManifest.xml

         <activity
            android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                <category android:name="android.intent.category.BROWSABLE" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="@string/kakao_scheme" android:host="@string/kakaolink_host" />
                <data android:scheme="kakao000000000000000000" />
            </intent-filter>
        </activity>
  • Share Mehtod 작성
        try {
            final KakaoLink kakaoLink = KakaoLink.getKakaoLink(a);
            final KakaoTalkLinkMessageBuilder kakaoTalkLinkMessageBuilder = kakaoLink.createKakaoTalkLinkMessageBuilder();
            AppActionInfo aai = new AppActionInfo(AppActionInfo.ACTION_INFO_OS.ANDROID, AppActionBuilder.DEVICE_TYPE.PHONE, "params=test", "referrer=kakaotalklink");
            AppActionBuilder aab = new AppActionBuilder().addActionInfo(aai);
            kakaoTalkLinkMessageBuilder.addAppButton("Open the app", aab.build());
            final String linkContents = kakaoTalkLinkMessageBuilder.build();
            kakaoLink.sendMessage(linkContents, a);
        } catch (KakaoParameterException e) {
            e.printStackTrace();
        }
  • 문자열 데이터를 받는 Activity
         Uri uri = getIntent().getData();
         if (uri != null){
              String param = uri.getQueryParameter("params");
              // do something              
         }

Android Kakaolink Ref
Android kakaolink guide

Clojure – ` syntax-quote와 ‘ quote의 차이점에 대해서

`syntax-quote와 'quote의 차이점에 대해서 알아보자.

첫번 째 syntax-quote는 namespace를 반환한다.
user>'x
x

user>`x
user/x

두번 째 syntax-quote는 ~unquote 또는 ~@unquote splicing 매크로를 사용할 수 있다.

user> `(this ~(symbol (str "i" "s" - "cool")))
(user/this is-cool)

user> '(this ~(symbol (str "i" "s" - "cool")))
(this (clojure.core/unquote (symbol (str "i" "s" - "cool"))))

위와 같이 두가지 차이점을 알수 있었다. 더 자세한 내용은 아래의 링크를 통해서 확인 할 수 있다.

reference

Clojure – ` syntax quote에 대해서

`syntax quote에 대해서 알아보자.

`와 연관있는 ~ unquote, 그리고 ~@unquote splicing이 있다.

위 두가지는 syntax quote를 이용하였을 때 그 반대의 영향을 주기 위해서 사용된다.

심볼을 심볼 그 자체로 활용하기 위해 사용된다. 'quote와 비슷하면서도 다르다. sytax quote 설명은 다른 장에서 알아보자.


user> (def five 5)
user/five
user>`five
user/five

p>한가지 기억하자. Clojure에서 모든 것들이 데이터이다. 위와 같이 quote를 사용하였을 때 평가되지 않고 심볼 자체 또는 리스트 자체로 출력이 되는 것을 볼수 있다.

그럼 왜 quote가 필요할까? 한가지 더 실험을 해보자.

(1 2 3)
Unhandled java.lang.ClassCastException
java.lang.Long cannot be cast to clojure.lang.IFn

` 위와 같이 에러가 발생된다. 이유는 현재 1이라는 심볼이 정의 되어 있지 않기 때문에 발생된다. (, )에 둘러 쌓여있을 때 첫번 째 argument는 심볼로 인식된다.

다시 quote를 활용해보자.


user>`(1 2 3)
(1 2 3)

에러 없이 정상으로 작동된다.

다음 장에서 `syntax-quote와 '의 차이점에 대해서 알아보자.

reference

Clojure – ~@ unquote splicing macro에 대해서

~@ unquote splicing macro에 대해서
unquoute splicing macro에 대해서 이해하기 위해서는 먼저 unquote macro에 대해서 알아야한다.

unquote splicing macro는 ~@로 기록하도록 하겠다.

~@는 form에 있는 심볼들을 모두 평가하기 위해 사용된다.

예제를 통해서 이해해보도록 하자.


user> (def three-and-four (list 3 4))

'user/three-and-four

위와 같이 심볼을 하나 만들었다.

그리고 그저 ~unqoute 로만 평가를 했을 때

user> `(1 ~three-and-four)
(1 (3 4))

위와 같이 평가된다.

여기서 unqoute splicing macro를 이용하여 폼 안에 있는 모든 심볼을 평가하고 싶을 때 아래와 같이 사용할 수 있다.


user> `(1 ~@three-and-four)
(1 3 4)

이상으로 unqoute macro와 unquote splicing macro에 대해서 알아보았다.

Clojure – ~ (unqoute)에 대해서

~ unqoute 매크로. ~ 는 unqoute 라고 읽는다. unqoute를 이해하기 위해서는 ` (syntax quote)에 대해서 알아야한다. 그럼 일단 syntax sqoute에 대해서 알아보자. 영어에서 syntax는 문법, 구문론 등으로 해석되며, qoute는 `으로 “인용하다.” 또는 “전달하다.”로 해석된다.

그럼 Clojure에서는 어떻게 사용될까?

클로저에서 메서드에 syntax qoute를 사용했을 때 말 그대로 메서드 자체(구문론 자체를)를 그대로 전달하라는 의미를 갖는다. 아래와 같다.


user> `(+ 1 1) (clojure.core/+ 1 1)

위와 같이 심볼 자체로 출력된다. unqoute는 이때 사용된다. 심볼을 평가하고 싶을때 아래와 같이 사용된다.


user>`~(+ 1 1) 2

심볼을 평가하고 싶을때 unqoute를 사용하면 된다.

위와 같이만 예로 들 경우 어디에서 사용해야 하는지 감이 제대로 잡히지 않는다. 좀 더 깊히 들어가보자.


user> (def three-and-four (list 3 4))

\'user/three-and-four

user> `(1 three-and-four)

어떤 값이 나올까? 예상하는 바와 같이 아래와 같이 출력된다. \'user/three-and-four

평가되지 않았다. 위 unqoute때문에 평가되지 않았다. 이때 1은 data이므로 평가하지 않아야하고 three-and-four만 평가를 하고 싶을때 아래와 같이 사용할 수 있다.

user>`(1 ~three-and-four)

어떻게 될까? (1 (2 3))


위와 같이 평가되어 위와 같이 출력된다.


다음장에서 ~@ unquote splicing macro를 사용하여 다른값을 유추해보자.