Welcome to FutureAppLaboratory

v=(*^ワ^*)=v

Learning RxSwift’s Github Sample

| Comments

Introduction

The first example RxSwift mock Github sign-up view controller. It checks availability of user name, password. Then simulate a sign-up process.

Listen User Input

This is easy to do with Rx framework. The rx_text field of UITextField is defined in RxCocoa is just what you want.

1
let username = usernameOutlet.rx_text

It tracks textViewDidChange wrap all changes into Observable, which is an basic event sequence can be Observed by Observer.

1
2
let each user input event as E, then this event sequence can be illustrated as
    ---E---E-E-----E-----

And handling tapping on a button is as easy as

1
let signupSampler = self.signupOutlet.rx_tap

Build Basic Observables

In a common sign-up process, we have to check

  1. User name is empty or includes illegal character
  2. User name has been signed up
  3. Password and Password Repeat are the same
  4. Request can be handled correctly or not

We build 4 observables, i.e. event stream to handle them separately.

1. Check User Name

This is a flatmap function for Observable. (Definition on flatmap can be found in this post. Functor, Monad, Applicative in Swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typealias ValidationResult = (valid: Bool?, message: String?)

func validateUsername(username: String) -> Observable<ValidationResult> {
    if username.characters.count == 0 {
        return just((false, nil))
    }

    // this obviously won't be
    if username.rangeOfCharacterFromSet(NSCharacterSet.alphanumericCharacterSet().invertedSet) != nil {
        return just((false, "Username can only contain numbers or digits"))
    }

    let loadingValue = (valid: nil as Bool?, message: "Checking availabilty ..." as String?)

    return API.usernameAvailable(username)
        .map { available in
            if available {
                return (true, "Username available")
            }
            else {
                return (false, "Username already taken")
            }
        }
        .startWith(loadingValue)
}

Update UI With Observer

| Comments

Sometimes I need to update UI from different Fragments. But it seems that writing update method in each Fragment is absurd if I have a lot of Fragment to handle with.

Here is a better solution by using Observer pattern, IMO.

Sample

    1. Define an EventObject, which is a event(or message) will be passed to Observer.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ViewPagerTabsUIEventObject extends EventObject {

    public enum EventType {
        LABEL_ON,
        LABEL_OFF,
    }

    private EventType mEventType;

    public ViewPagerTabsUIEventObject(Object source, EventType eventType) {
        super(source);
        mEventType = eventType;
    }

    public static final String TAG = "ViewPagerTabsUIEventObject";

    public EventType getEventType() {
        return this.mEventType;
    }
}
    1. Define an Observable, which will dispatch event or message to Observer.
1
2
3
4
5
6
7
8
9
10
11
12
13
public class ViewPagerTabsObservable extends Observable {

    public static final String TAG = "ViewPagerTabsObservable";

    public static ViewPagerTabsObservable sInstance = new ViewPagerTabsObservable();

    public ViewPagerTabsObservable() { }

    public void notice(ViewPagerTabsUIEventObject eventObject) {
        setChanged();
        notifyObservers(eventObject);
    }
}
    1. Caller. Use a shared instance of Observable to dispatch event to Observer.
1
2
ViewPagerTabsUIEventObject eventObject = new ViewPagerTabsUIEventObject(this, EventType.LABEL_OFF);
ViewPagerTabsObservable.sInstance.notice(eventObject);
    1. Receiver, which implements Observer interface.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ViewPagerTabs extends LinearLayout implements Observer {

    /* ... */

    @Override
    public void update(Observable observable, Object data) {
        // TODO Auto-generated method stub
        ViewPagerTabsUIEventObject eventObject = (ViewPagerTabsUIEventObject) data;
        switch (eventObject.getEventType()) {
        case LABEL_OFF:
            // do something
            break;
        case LABEL_ON:
            // do something else
            break;
        default:
            break;
        }
    }

}

Functor, Monad, Applicative in Swift

| Comments

Some Implementation in Swift.

Functor

Let $C$ be a constructor which takes a variable as parameter.

$C$ is a Functor if $C$ has the following ability.

For example, we can write such a function functor or map.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Box<T> {
    var content: T
    init(content: T) {
        self.content = content
    }

    func functor<U>(_ relation: (T) -> U) -> Box<U> {
        return Box<U>(content: relation(self.content))
    }
}

func functor<T, U>(box: Box<T>, relation: (T) -> U) -> Box<U> {
    return Box(content: relation(box.content))
}

let a = Box(content: 1)
let b = functor(box: a, relation: { $0 + 100 }).content
b // 101

And we can chain functors like this.

1
2
3
let addup: (Int)->Int = { $0 + 1 }
let c = a.functor(addup).functor(addup).content
c // 3

We can also do it like Haskell

1
2
3
4
5
6
7
8
9
10
precedencegroup chainopts {
    associativity: left
}
infix operator <^>: chainopts
func <^><T, U>(a: Box<T>, f: (T) -> U) -> Box<U> {
    return a.functor(f)
}

let d = a <^> addup <^> addup
d.content // 3

Monad

$C$ is a Monad if $C$ has the following ability. This is also called as flatmap.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Box<T> {
    var content: T
    init(content: T) {
        self.content = content
    }

    func monad<U>(_ relation: (T) -> Box<U>) -> Box<U> {
        return relation(self.content)
    }
}

func monad<T, U>(box: Box<T>, relation: (T) -> Box<U>) -> Box<U> {
    return relation(box.content)
}

let e = monad(box: a, relation: { Box(content: $0 + 1000) } ).content
e // 1001
let addupAndWrap: (Int)->Box<Int> = { Box(content: $0 + 1000) }
let f = a.monad(addupAndWrap).monad(addupAndWrap).content
f // 2001

Haskell-like version.

1
2
3
4
5
6
infix operator >>-: chainopts
func >>-<T, U>(a: Box<T>, f: (T) -> Box<U>) -> Box<U> {
    return a.monad(f)
}
let g = a >>- addupAndWrap >>- addupAndWrap
g.content // 2001

Applicative

$C$ is a Applicative if $C$ has the following ability.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Box<T> {
    var content: T
    init(content: T) {
        self.content = content
    }

    func apply<U>(_ relation: Box<(T) -> U>) -> Box<U> {
        return Box<U>(content: relation.content(self.content))
    }
}

func apply<T, U>(box: Box<T>, relation: Box<(T) -> U>) -> Box<U> {
    return Box(content: relation.content(box.content))
}

let h = apply(box: a, relation: Box(content: {$0 + 10000} )).content
h // 10001
let anBoxContainsAddup: Box<(Int)->Int> = Box(content: {$0 + 10000} )
let i = a.apply(anBoxContainsAddup).apply(anBoxContainsAddup).content
i // 20001

Haskell-like version.

1
2
3
4
5
6
infix operator <*>: chainopts
func <*><T, U>(a: Box<T>, f: Box<(T) -> U>) -> Box<U> {
    return a.apply(f)
}
let j = a <*> anBoxContainsAddup <*> anBoxContainsAddup
j.content // 20001

Remove Border From Android Dialog

| Comments

In android development, Dialog are DialogFragment are very common UI parts. They come with a default border, with black or white background depending on android system version.

IMAGE_A

The default border, which is actually a drawable, is defined in android themes file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<style name="Theme.Dialog">
    <item name="windowFrame">@null</item>
    <item name="windowTitleStyle">@style/DialogWindowTitle</item>
    <item name="windowBackground">@drawable/panel_background</item>
    <item name="windowIsFloating">true</item>
    <item name="windowContentOverlay">@null</item>
    <item name="windowAnimationStyle">@style/Animation.Dialog</item>
    <item name="windowSoftInputMode">stateUnspecified|adjustPan</item>
    <item name="windowCloseOnTouchOutside">@bool/config_closeDialogWhenTouchOutside</item>
    <item name="windowActionModeOverlay">true</item>
    <item name="colorBackgroundCacheHint">@null</item>
    <item name="textAppearance">@style/TextAppearance</item>
    <item name="textAppearanceInverse">@style/TextAppearance.Inverse</item>
    <item name="textColorPrimary">@color/primary_text_dark</item>
    <item name="textColorSecondary">@color/secondary_text_dark</item>
    <item name="textColorTertiary">@color/tertiary_text_dark</item>
    <item name="textColorPrimaryInverse">@color/primary_text_light</item>
    <item name="textColorSecondaryInverse">@color/secondary_text_light</item>
    <item name="textColorTertiaryInverse">@color/tertiary_text_light</item>
    <item name="textColorPrimaryDisableOnly">@color/primary_text_dark_disable_only</item>
    <item name="textColorPrimaryInverseDisableOnly">@color/primary_text_light_disable_only</item>
    <item name="textColorPrimaryNoDisable">@color/primary_text_dark_nodisable</item>
    <item name="textColorSecondaryNoDisable">@color/secondary_text_dark_nodisable</item>
    <item name="textColorPrimaryInverseNoDisable">@color/primary_text_light_nodisable</item>
    <item name="textColorSecondaryInverseNoDisable">@color/secondary_text_light_nodisable</item>
    <item name="textColorHint">@color/hint_foreground_dark</item>
    <item name="textColorHintInverse">@color/hint_foreground_light</item>
    <item name="textColorSearchUrl">@color/search_url_text</item>
    <item name="textAppearanceLarge">@style/TextAppearance.Large</item>
    <item name="textAppearanceMedium">@style/TextAppearance.Medium</item>
    <item name="textAppearanceSmall">@style/TextAppearance.Small</item>
    <item name="textAppearanceLargeInverse">@style/TextAppearance.Large.Inverse</item>
    <item name="textAppearanceMediumInverse">@style/TextAppearance.Medium.Inverse</item>
    <item name="textAppearanceSmallInverse">@style/TextAppearance.Small.Inverse</item>
    <item name="listPreferredItemPaddingLeft">10dip</item>
    <item name="listPreferredItemPaddingRight">10dip</item>
    <item name="listPreferredItemPaddingStart">10dip</item>
    <item name="listPreferredItemPaddingEnd">10dip</item>
    <item name="preferencePanelStyle">@style/PreferencePanel.Dialog</item>
</style>

panel_background in <item name="windowBackground">@drawable/panel_background</item> is a 9-patch image file.

IMAGE_B

We can remove the border by defining windowBackground property, in our own custom style file, to replace the default one.

1
2
3
<style name="CustomDialogStyle" parent="@android:style/Theme.Dialog">
   <item name="android:windowBackground">@drawable/custom_dialog_background</item> // a custom 9-patch or something else
</style>

Or, use the following one-line code solution.

1
dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);

Also, there are other dialog styles defined in android themes file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- Variant of {@link Theme_Dialog} that does not include a frame (or background).
     The view hierarchy of the dialog is responsible for drawing all of
     its pixels. -->
<style name="Theme.Dialog.NoFrame">
    <item name="windowBackground">@color/transparent</item>
    <item name="windowFrame">@null</item>
    <item name="windowContentOverlay">@null</item>
    <item name="windowAnimationStyle">@null</item>
    <item name="backgroundDimEnabled">false</item>
    <item name="windowIsTranslucent">true</item>
    <item name="windowNoTitle">true</item>
    <item name="windowCloseOnTouchOutside">false</item>
</style>

<!-- Default theme for alert dialog windows (on API level 10 and lower), which is used by the
     {@link android.app.AlertDialog} class.  This is basically a dialog
     but sets the background to empty so it can do two-tone backgrounds. -->
<style name="Theme.Dialog.Alert">
    <item name="windowBackground">@color/transparent</item>
    <item name="windowTitleStyle">@style/DialogWindowTitle</item>
    <item name="windowContentOverlay">@null</item>
    <item name="itemTextAppearance">@style/TextAppearance.Large.Inverse</item>
    <item name="textAppearanceListItem">@style/TextAppearance.Large.Inverse</item>
    <item name="textAppearanceListItemSmall">@style/TextAppearance.Large.Inverse</item>
    <item name="textAppearanceListItemSecondary">@style/TextAppearance.Small.Inverse</item>
</style>

Codility Calcium 2015

| Comments

Introduction

This is an solution in java of Codility Calcium 2015.

You can find problem definition here. https://codility.com/programmers/challenges/

The key logic is using binary search to limit the answer area.

For each iteration of binary search, detail steps are written in the following code.

Optimization for 100% solution

This problem is very time strict for java.

For large data set, my old answer which use many Collections always runs for 6+ seconds, but time limit is only 4 seconds.

So I replace almost all of them with array in heavy calculation part, to reduce time consuming of GC.

This results an 3+ seconds for each iteration of binary search.

https://codility.com/cert/view/certXKYPW7-D87MFQHQEGCKWRV9/details

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import java.util.*;

public class TrafficCamera {

    private int rootIndex = 0;
    private int largestId = 0;
    private int sortedSubtreeDiameters[] = new int[50001];
    private Node nodeMap[] = new Node[50001];
    private boolean visited[] = new boolean[50001];
    private int diameter[] = new int[50001];

    static class Node {
        public int id;
        public List<Integer> edgeList;
        public Node(int v) {
            this.id = v;
            edgeList = new ArrayList<Integer>();
        }
        public void addEdge(Integer t) {
            edgeList.add(t);
        }
    }

    public int solution(int[] A, int[] B, int K) {
        int n = A.length;
        for (int i = 0; i < n; i++) {
            if (nodeMap[A[i]] == null) {
                nodeMap[A[i]] = new Node(A[i]);
            }
            if (nodeMap[B[i]] == null) {
                nodeMap[B[i]] = new Node(B[i]);
            }
            final Node na = nodeMap[A[i]];
            final Node nb = nodeMap[B[i]];
            na.addEdge(B[i]);
            nb.addEdge(A[i]);
            largestId = Math.max(largestId, A[i]);
            largestId = Math.max(largestId, B[i]);
        }

        rootIndex = A[0];
        int res = Integer.MAX_VALUE;

        int low = 0, high = Math.min(900, largestId);
        while (low <= high) {
            int mid = (low + high) / 2;
            if (isAvailabel(K, mid)) {
                res = Math.min(res, mid);
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        return res;
    }

    public boolean isAvailabel(int cameraLimit, int notCoveredLength) {
        for (int i = 0; i < visited.length; i++) visited[i] = false;
        for (int i = 0; i < diameter.length; i++) diameter[i] = -1;
        if (dfs(nodeMap[rootIndex], notCoveredLength) > cameraLimit) {
            return false;
        }
        return true;
    }

    private int dfs(Node node, int limit) {
        if (node == null || visited[node.id]) {
            return 0;
        }

        visited[node.id] = true;

        int counter = 0;
        for (Integer id : node.edgeList) {
            counter += dfs(nodeMap[id], limit);
        }

        /*
                      @
                    /  \
                  @      @(current)
                / \    / \\\
               @  @    a b c d

         step 1: Sort diameters of subtrees in reverse order, store them in an array
         step 2: For each adjacent pair in the sorted array, e.g. (a, b), (b, c), (c, d).
            If a + b + 2 > limit, we break the edge connecting current node to a, and counter plus 1.
            Do the same for (b, c) and (c, d). Note that, if (a, b) passed the test, we don't need to anymore
            because (a, b) is the largest pair of them.
         step 3: For last child d. or if current node has only one child.
            If d + 1 > limit, we break the edge connecting current node to a, and counter plus 1.
         step 4: Set diameter of current node to the largest one of the remains(not broken ones).

         */

        /* step 1 */
        int n = 0;
        for (Integer id : node.edgeList) {
            if (diameter[id] != -1) {
                sortedSubtreeDiameters[n++] = (diameter[id]);
            }
        }

        Arrays.sort(sortedSubtreeDiameters, 0, n);
        for (int i = 0; i < n / 2; i++) { // reverse order
            int temp = sortedSubtreeDiameters[i];
            sortedSubtreeDiameters[i] = sortedSubtreeDiameters[n - 1 - i];
            sortedSubtreeDiameters[n - 1 - i] = temp;
        }

        /* step 2 */
        int maxDiameterAfterRemoval = -1;
        for (int i = 0; i < n - 1; i++) {
            if (sortedSubtreeDiameters[i] + sortedSubtreeDiameters[i+1] + 2 > limit) {
                counter++;
            } else {
                maxDiameterAfterRemoval = Math.max(maxDiameterAfterRemoval, sortedSubtreeDiameters[i]);
                break; // Even the largest pair is in the limit, we don't need check the remaining pairs.
            }
        }

        /* step 3 */
        if (n >= 1) {
            int i = n - 1;
            if (sortedSubtreeDiameters[i] + 1 > limit) {
                counter++;
            } else {
                maxDiameterAfterRemoval = Math.max(maxDiameterAfterRemoval, sortedSubtreeDiameters[i]);
            }
        }

        /* step 4 */
        // if subtrees are all removed, we can treat current node as a leaf, so diameter becomes 0
        if (maxDiameterAfterRemoval == -1) {
            diameter[node.id] = 0;
        } else { // if still some subtrees remain, then set current node's diameter as one plus the largest
            diameter[node.id] = maxDiameterAfterRemoval + 1;
        }

        return counter;
    }

}

Longest Increasing Subsequence

| Comments

Introduction

In computer science, the longest increasing subsequence problem is to find a subsequence of a given sequence in which the subsequence’s elements are in sorted order, lowest to highest, and in which the subsequence is as long as possible.

For example, a longest increasing subsequence of 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 is 0, 2, 6, 9, 11, 15.

An O(N2) Solution

We define a $dp$ table, which $dp[i]$ is the length of a longest subsequence which ends at $inputs[i]$.

For each $inputs[i]$, we search every inputs before it, and choose the longest possible $dp$ value from them, fill it in $dp[i]$.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private int[] inputs = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15};
private int[] dp = new int[128];

public int solution() {
    for (int i = 0; i < dp.length; i++) { dp[i] = 0; }
    dp[0] = 1;

    for (int i = 1; i < inputs.length; i++) {
        int longest = 1;
        for (int j = 0; j < i; j++) {
            if (inputs[i] > inputs[j]) {
                longest = Math.max(longest, dp[j] + 1);
            }
        }
        dp[i] = longest;
    }
    return dp[inputs.length - 1];
}

An O(N*Log(N)) Solution

We define a $seq$ table, which $seq[i]$ is the ending number of subsequence whose length is $i$.

Note that, $seq$ is always in increasing order.

Because if these exist $i < j$ and $seq[i] > seq[j]$, which means a longer subsequence end with a smaller number.

Then we could generate a new subsequence, which length is $i$, by removing $j - i$ numbers from tail of $j$-length subsequence. The ending number of the new subsequence will be smaller than $seq[i]$.

Therefore, we can use binary search in each iteration, to find the largest $seq[k]$ which is smaller than $inputs[i]$. If $k$ can be found ($k > -1$), then we update the number stored in $seq[k]$ with $inputs[i]$ if $inputs[i] < seq[k]$.

This yields an O(N*Log(N)) solution.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private int[] seq = new int[128];

public int solution2() {
    for (int i = 0; i < seq.length; i++) { seq[i] = Integer.MAX_VALUE; }

    int res = Integer.MIN_VALUE;
    for (int i = 0; i < inputs.length; i++) {
        int longest = 1;
        int bestSeqLength = binarySearch(seq, 0, i, inputs[i]);
        if (bestSeqLength > -1) {
            longest = bestSeqLength + 1;
        }
        seq[longest] = Math.min(seq[longest], inputs[i]);
        res = Math.max(res, longest);
    }
    return res;
}

public int binarySearch(int[] array, int begin, int end, int target) {
    int s = begin;
    int t = end;
    int m = s;
    int result = -1; // !!!
    while (s <= t) {
        m = (s + t) / 2;
        if (array[m] < target) {
            s = m + 1;
            result = m; // result index, which array.get(result) is most close to & less than target
        } else if (array[m] == target) {
            return m;
        } else {
            t = m - 1;
        }
    }
    return result;
}

Swift String Operations

| Comments

String operations of Swift seems to be easy to handle, but we should take care of them in development.

For example, this is a common code snippet which stays on the top spot, by searching ‘swift substring’ from www.google.co.jp.

1
2
3
4
5
extension String {
    public func substring(location:Int, length:Int) -> String! {
        return (self as NSString).substringWithRange(NSMakeRange(location, length))
    }
}

But, it is not correct. NSString’s implementation is based on UTF-16 and handing index for it is just confusing.

Take a look at the following test.

IMGAE_A

Because index in NSString format is different from index in String format. The text is counted as length 2 in NSString, but only 1 in String.

So using substring in NSString only get half of it, from its binary expression. Then certainly, it cannot be decoded correctly.

Swift Online Guide has a detail explanation for this problem.

We should use String.Index to handle different byte length of each character, and also decomposed and precomposed characters.

Because String in Swift is implemented as Unicode Scalar, or UTF-32, is always 4-byte long for each character and it default behaviour can handle composed characters.

1
2
3
4
5
6
7
8
9
public func substring2(location: Int, length:Int) -> String! {
    assert(location >= 0, "OMG")
    assert(location + length <= count(self), "OMG again")
    var startIndex = self.startIndex
    startIndex = advance(startIndex, location)
    var res = self.substringFromIndex(startIndex)
    var endIndex = advance(startIndex, length)
    return res.substringToIndex(endIndex)
}

IMAGE_B

For further reading about encoding, I recommend this page. http://www.objc.io/issues/9-strings/unicode/#utf-8

Surrogate Support for Swift’s String

| Comments

String in Swift does not support UTF16 surrogate in default.

From Swift Official Guide

A Unicode scalar is any Unicode code point in the range U+0000 to U+D7FF inclusive or U+E000 to U+10FFFF inclusive. Unicode scalars do not include the Unicode surrogate pair code points, which are the code points in the range U+D800 to U+DFFF inclusive.

Therefore, I wrote a support class for it. :)


SwiftSurrogate on Github

Now you can decode Swift String from UTF16 surrogate pair like this.

1
2
var emoji1 = SwiftySurrogate.decodeFromSurrogatePair(surrogatePair: "D83D:DCC9")
var emoji2 = SwiftySurrogate.decodeFromSurrogatePair(high: 0xD83C, low: 0xDF80)

Generics Enum in Swift

| Comments

If we want handle different data type, according to the result of calling some APIs. We may need the code like this.

1
2
3
4
enum APIResponse<JsonType, ErrorMsgType> {
    case Success(JsonType)
    case Fail(ErrorMsgType)
} // compile error

But the code above cannot be compiled.

1
2
// integer overflows when converted from 'Builtin.Int32' to 'Builtin.Int8'MyPlayground.playground:148:6: error: unimplemented IR generation feature non-fixed multi-payload enum layout
//     enum APIResponse<JsonType, ErrorMsgType> {

How can we actually do this

We can use Container Class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class SuccessContainer<T> {
    init (t : T) {
        // ...
    }
}

class ErrorContainer<T> {
    init (t : T) {
        // ...
    }
}

enum APIResponse<JsonType, ErrorMsgType> {
    case Success(SuccessContainer<JsonType>)
    case Fail(ErrorContainer<ErrorMsgType>)
}

// usage

func GenAPIResponseSuccess<JsonType, ErrorMsgType>(json : JsonType) -> APIResponse<JsonType, ErrorMsgType> {
    return .Success(SuccessContainer<JsonType>(t: json))
}

func GenAPIResponseFail<JsonType, ErrorMsgType>(errorMsg : ErrorMsgType) -> APIResponse<JsonType, ErrorMsgType> {
    return .Fail(ErrorContainer<ErrorMsgType>(t: errorMsg))
}

func callbackFromAPI(response: String) -> APIResponse<String, String> {
    if (response.hasPrefix("success")) {
        return GenAPIResponseSuccess(response) // can be parsed to json
    } else {
        return GenAPIResponseFail(response) // just a plain error msg
    }
}

callbackFromAPI("success. I'm Json")
callbackFromAPI("404 or something else")

But it is awful to write bunch of code for creating container classes, for each type you need.

Better Solution?

Here is a very useful lib. https://github.com/robrix/Box

It provide a container class called Box

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public final class Box<T>: BoxType, Printable {
    /// Initializes a `Box` with the given value.
    public init(_ value: T) {
        self.value = value
    }


    /// Constructs a `Box` with the given `value`.
    public class func unit(value: T) -> Box<T> {
        return Box(value)
    }


    /// The (immutable) value wrapped by the receiver.
    public let value: T

    /// Constructs a new Box by transforming `value` by `f`.
    public func map<U>(@noescape f: T -> U) -> Box<U> {
        return Box<U>(f(value))
    }


    // MARK: Printable

    public var description: String {
        return toString(value)
    }
}

Finally

We can rewrite above code into this. Much more elegant.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enum APIResponse2<JsonType, ErrorMsgType> {
    case Success(Box<JsonType>)
    case Fail(Box<ErrorMsgType>)
}

func GenAPIResponseSuccess2<JsonType, ErrorMsgType>(json : JsonType) -> APIResponse2<JsonType, ErrorMsgType> {
    return .Success(Box<JsonType>(json))
}

func GenAPIResponseFail2<JsonType, ErrorMsgType>(errorMsg : ErrorMsgType) -> APIResponse2<JsonType, ErrorMsgType> {
    return .Fail(Box<ErrorMsgType>(errorMsg))
}

func callbackFromAPI2(response: String) -> APIResponse2<String, String> {
    if (response.hasPrefix("success")) {
        return GenAPIResponseSuccess2(response) // can be parsed to json
    } else {
        return GenAPIResponseFail2(response) // just a plain error msg
    }
}

callbackFromAPI2("success. I'm Json")
callbackFromAPI2("404 or something else")

checkstack.plを遊んでみた

| Comments

偶には再帰メソッドの消費メモリーが気になるので、

どうやって調べるのを調べたら、Linux OSに付いているcheckstack.plを見つけた。

ソースをgistに上げました。checkstack.pl

ちなみに、154行目のnext if ($size < 100);をコメントアウト済み。

そうすれば、スタックサイズが100以下のメソッドもリストアップしてくれた。

テスト用のソース

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using namespace std;

struct Point {
    float px;
    float py;
    Point(float _x, float _y):px(_x),py(_y) {}
};

int func() {
    int i = 4;
    int j = 4;
    return i + j;
}

void func_arr_int_1() {
    int arr[1];
    arr[0] = 0xFFFFFFFF;
}

void func_arr_int_2() {
    int arr[2];
}

void func_arr_int_3() {
    int arr[3];
}

void func_arr_int_4() {
    int arr[4];
}

void func_point() {
    Point p = Point(0.0f, 0.0f);
}

int main(int argc, char* argv[]) {
    func();
    func_arr_int_1();
    func_arr_int_2();
    func_arr_int_3();
    func_arr_int_4();
    func_point();
    return 0;
}

使う

macなのでobjdumpではなくgobjdumpを使う。homebrewでインストールすると便利

g++ stack_test.cpp
gobjdump -d a.out | checkstack

結果

0x000100000e14 __Z14func_arr_int_2v []:   16
0x000100000e33 __Z14func_arr_int_2v []:   16
0x000100000e44 __Z14func_arr_int_3v []:   32
0x000100000e63 __Z14func_arr_int_3v []:   32
0x000100000e74 __Z14func_arr_int_4v []:   32
0x000100000e93 __Z14func_arr_int_4v []:   32
0x000100000ea4 __Z10func_pointv []:   16
0x000100000ebe __Z10func_pointv []:   16
0x000100000ed4 _main []:    32
0x000100000f09 _main []:    32
0x000100000f14 __ZN5PointC1Eff []:   16
0x000100000f39 __ZN5PointC1Eff []:   16

今のコンパイラって結構やってくれるな。

func1ではレジストで操作したからスタック消費はなし。

func_arr_int_1がスタック消費がないのは驚いた。size = 1のarrayは最適化されてなくなったみたい。

x64マシンなので、メモリー最小単位は16byteになるのか?

func_arr_int_2は16byte。func_arr_int_3は実際24byteだと思うが、スタックサイズは32byteになった。メモリーは2単位取ったね。

checkstack.plの原理

 elsif ($arch =~ /^x86(_64)?$/ || $arch =~ /^i[3456]86$/) {
    #c0105234:       81 ec ac 05 00 00       sub    $0x5ac,%esp
    # or
    #    2f60:    48 81 ec e8 05 00 00       sub    $0x5e8,%rsp
    $re = qr/^.*[as][du][db]    \$(0x$x{1,8}),\%(e|r)sp$/o;
    $dre = qr/^.*[as][du][db]    (%.*),\%(e|r)sp$/o;
}

逆コンパイルされたコードの中のadd, sub, esp, rspを探して、スタックサイズを計算するらしい。

参考:http://0xcc.net/blog/archives/000115.html