Elmメモ - 文字列をIPアドレスに変換(1) splitを用いる方法

IPv4アドレスの文字列"192.168.1.1"をパースする方法を考える。IPAddrの内部表現は次のようにする。 1 type IPAddr = IPAddr Int Int Int Int 思いつくのは次の2通り。 ピリオドでsplitして、整数値に変換する。 パーサを利用する。 いずれにしても結構面倒。この記事では前者だけやる。 準備 適当なディレクトリで次のコマンドを実行。 $ elm init $ elm install elm/parser src/IPAddr.elmを作り、内容を以下の通りにする。 1 2 3 module IPAddr exposing (..) type IPAddr = IPAddr Int Int Int Int $ elm repl > import IPAddr exposing (..) 方針 次の処理を行う関数をfromStringとして定義する。 文字列を.でsplitする。 Listの要素数が4でなければ失敗。 Listの各要素にString.toIntを適用し、どれか一つでも失敗すれば全体としても失敗。 Listを[a,b,c,d]としたとき、IPAddr a b c dを返す。 traverseの実装 3の処理は、次の関数として抽象化できる: リストの各要素にfを適用し、その結果すべてがJustを返したときだけ、全体の結果を返す。 1 traverse : (a -> Maybe b) -> List a -> Maybe List b 原始的な実装 なるべくfoldrとかを使わずに書こうとするとこんな感じになる。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 traverse : (a -> Maybe b) -> List a -> Maybe (List b) traverse f list = case list of [] -> Just [] x::xs -> case traverse f xs of Nothing -> Nothing Just ys -> case f x of Nothing -> Nothing Just y -> Just (y::ys) case文を使ってネストが深くなってくると、Haskellみたいなパターンマッチが欲しくなってくる。 ...

2020-01-05 · (updated 2020-01-05) · 3 min · 437 words

Elmメモ - ランダムな位置に円を描画する

乱数の練習に。 準備 プロジェクト用のディレクトリを適当に作り、そこで以下のコマンドを実行。 $ elm init 必要なモジュールを入れる。 $ elm install elm/svg $ elm install elm/random Main.elmを作成し、最低限の文を定義しておく。 1 2 3 4 5 6 7 module Main exposing (..) import Browser import Svg exposing (..) import Svg.Attributes as SA exposing (..) import Svg.Events as SE exposing (..) import Random 円の描画 こんな感じの円を描画する。 1 SVGでは次のように書く。 1 2 3 4 5 6 <svg width="100px" height="100px"> <g transform="translate(50, 50)"> <circle r="10" fill="white" stroke="black" /> <text text-anchor="middle" dominant-baseline="central">1</text> </g> </svg> 円の情報で必要なのは次の4つ: x座標 y座標 半径 text要素の文字列 そこで円は次のように定義する。 1 2 3 4 5 6 type alias Circle = { r: Float , x: Float , y: Float , text: String } Elmでは宣言的にSVGやHTMLを書けるので、SVGの文法とほとんど似た構造でかける。直感的で嬉しい。 ...

2020-01-01 · (updated 2020-01-05) · 5 min · 904 words

JavaScript/Elm ビット演算のときにはまったこと

結論 JavaScriptにおいて、>>>以外のビット演算は32ビット符号付き整数値として扱われる。 → 例えば&|^~の計算前に、オペランドに型変換が起こる(ソース)。 JSにおいて数字はNumberという型しかないが、ビット演算のときだけ32ビット整数値に変換されるっぽい JavaScriptにおいて、x >>> 0を使うと符号なし整数になる。 負数を2で割り続けても、コンピュータにおける2進表現にはならない。 これはすごく当たり前だった コンピュータにおける2進数表現にしたいなら,論理シフトを使うこと。 ElmはJavaScriptに変換されるので、上の事実はすべてElmでも当てはまる。 各種ビット演算は、JSの演算をそのまま使っているっぽい(ソース) 検証コード $ elm init src/MyBitwise.elmを作成し、内容を以下のようにする。 1 2 3 4 5 6 7 8 9 10 11 12 13 module MyBitwise exposing (..) import Bitwise toBinaryString : Int -> String toBinaryString x = let bit = Bitwise.and x 1 rem = Bitwise.shiftRightZfBy 1 x in if rem > 0 then (toBinaryString rem) ++ (String.fromInt bit) else String.fromInt bit elm replを起動し、試す。まず必要なモジュールをimportする。 $ elm repl > import Bitwise > import MyBitwise exposing (..) 232-1 = 4294967295を2進表示すると、1が32個並んだ数になる。32ビット整数の2の補数表現では、-1と4294967295は同じ表現方法になる。 > toBinaryString 4294967295 "11111111111111111111111111111111" : String > toBinaryString -1 "11111111111111111111111111111111" : String Bitwise.andの計算結果は符号付き整数値とみなされるので、以下では4294967295ではなく-1と出力される。 ...

2019-12-31 · (updated 2019-12-31) · 3 min · 440 words

iPhoneアプリ開発メモ - 棒グラフの作成(Chartsの利用)

今度は外部ライブラリChartsを利用して、棒グラフを作成してみる。 目標 値が最大のデータは色をオレンジにする アニメーションがある 棒グラフの上に値を表示する ページ切り替えができる棒グラフを作る タップしたらイベントを発生させる 1〜3、5は機能としてある。4だけ頑張って作る。思い通りのレイアウトにするためにはプロパティとかドキュメントとかを漁る必要があるが、どこにどのプロパティがあるのかは大体予想できる。 ChartDataSet.colorsで各棒の色を変更できる。 BarChartView.animate(yAxisDuration:)を利用。 BarChartView.drawValueAboveBarEnabled = trueとする。表示形式を変更するためにはChartDataSet.valueFormatterにフォーマット用のオブジェクトを指定する。 ScrollViewの中ににBarChartViewを複数配置。 ChartViewDelegateを利用。 その他デフォルトの設定だと表示する情報量が多すぎるので、いくつかのプロパティをいじる。 Chartsのインストール まず、CocoaPodsがインストールされていることが前提。 プロジェクトフォルダで以下のコマンドを実行。 1 $ pod init podfileが作成されるので、それを編集する。use_frameworks!の下に以下の記述を追加。 1 pod 'Charts' プロジェクトフォルダで以下のコマンドを実行。 1 $ pod install 以降、プロジェクトはプロジェクト名.xcodeprojではなくプロジェクト名.xcworkspaceから開く。 基本 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 import UIKit import Charts struct BarChartModel { let value: Int let name: String } class ViewController: UIViewController { let barItems = [ (7, "太郎"), (1, "次郎"), (2, "三郎"), (6, "四郎"), (3, "五郎"), (9, "六郎"), (2, "七郎"), (3, "八郎"), (1, "九郎"), (5, "十郎"), (1, "十一郎"), (1, "十二郎"), (6, "十三郎") ] override func viewDidLoad() { super.viewDidLoad() let barChartView = createBarChartView() self.view.addSubview(barChartView) barChartView.translatesAutoresizingMaskIntoConstraints = false barChartView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 80).isActive = true barChartView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -80).isActive = true barChartView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true barChartView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true } private func createBarChartView() -> BarChartView { let barChartView = BarChartView() barChartView.data = createBarChartData(of: barItems.map({BarChartModel(value: $0.0, name: $0.1)})) return barChartView } private func createBarChartData(of items: [BarChartModel]) -> BarChartData { let entries: [BarChartDataEntry] = items.enumerated().map { let (i, item) = $0 return BarChartDataEntry(x: Double(i), y: Double(item.value)) } let barChartDataSet = BarChartDataSet(entries: entries, label: "Label") let barChartData = BarChartData(dataSet: barChartDataSet) return barChartData } } これだけの記述で以下の棒グラフが描ける。 ...

2019-12-29 · (updated 2019-12-29) · 4 min · 685 words

iPhoneアプリ開発メモ - 棒グラフの作成(UIStackView) (2) アニメーションとタップ処理

棒グラフをアニメーションさせたり、タップしたら詳細画面に遷移するようにしたい。 (寄り道) StackViewのアニメーション 自分がいままでやったことがあるのは以下のような感じで、frameやlayer.positionをいじるパターン。 1 2 3 4 UIView.animate(withDuration: 1, animations: { view.position.x += 100 view.frame.size.height += 20 }); ただし、これをAutoLayoutと混ぜて使おうとすると動かなかったり、動いたとしても変な挙動を起こす。そもそも、AutoLayoutは制約を設定して位置やサイズを決定する仕組みで、frameは位置やサイズを手動で決める仕組み。これが競合を起こすのは当たり前な気もする。 StackViewはframeを設定しても何も反応しない。これは内部的にAutoLayoutっぽいことをやっているからなのかもしれない。例えば次のようにしてもStackViewの子要素は変更されない。 1 2 subView.frame.size.height = 100 stackView.addArrangedSubview(subView) その代わり、次のようにすると、ちゃんと子要素の高さは100になる。 1 2 subView.heightAnchor.constraint(equalToConstant: 100).isActive = true stackView.addArrangedSubview(subView) よって、StackViewでアニメーションするためには、AutoLayoutでのアニメーションの方法を知る必要がある。 AutoLayoutでのアニメーション 例えば、「ボタンを押すと長方形が0から伸びる」アニメーションを実現したい。 まずは次のように、高さ制約を0に設定しておく。ただし、それを何か変数に入れておく。 1 2 3 4 5 var constraint: NSLayoutConstraint view.addSubview(view0) constraint = view0.heightAnchor.constraint(equalToConstant: 0) constraint.isActive = true アニメーションをしたいタイミングで、次のように書けば良い。 1 2 3 4 UIView.animate(withDuration: 1, animations: { constraint.constant = 100 view.layoutIfNeeded() }) constraintにはconstantプロパティがあるので、そこで制約の定数を変更できる。layoutIfNeededは、アプリにレイアウト変更を直ちにさせるメソッド。 画面遷移後、下から長方形が伸びてくる処理は次のように書ける。 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 class ViewController: UIViewController { var constraint: NSLayoutConstraint! override func viewDidLoad() { super.viewDidLoad() let myView = UIView() myView.backgroundColor = .systemIndigo view.addSubview(myView) myView.translatesAutoresizingMaskIntoConstraints = false myView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true myView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true myView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true constraint = myView.heightAnchor.constraint(equalToConstant: 0) constraint.isActive = true } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) UIView.animate(withDuration: 3, animations: { self.constraint.constant = 200 self.view.layoutIfNeeded() }) } } StackViewのアニメーション 上の事実がわかってしまうと、StackViewのアニメーションもそこまで難しくない。子要素すべてのConstraintを変更すれば良いだけ。ただしそのために、各Constraintを保存した配列を用意しておく必要がある。 ...

2019-12-26 · (updated 2019-12-26) · 7 min · 1328 words

iPhoneアプリ開発メモ - 棒グラフの作成(UIStackView) (1)

前に頑張ってCoreGraphicsを使って棒グラフを描いたが、やはりViewを棒に見立てて扱った方が良さそうだ。考えられる利点は次の3つ。 タップ時に何かアクションを起こせる。例えば、棒グラフをタップしたら、そのデータに関する詳細ページに飛ぶ、などの処理が実装できる。 アニメーションについてのコードを描きやすい。例えば棒グラフの高さを0から伸ばしていくアニメーションが実現できる。 StackViewで棒を管理すれば、棒のサイズや棒同士の間隔を自動で設定してくれる これはやるしかない。 基本的にはこちらを参考にしながら進めていく。 UIStackViewをコード上で使う基本 とりあえず使い方を確認する。 Main.storyboard こんな感じにする。 ViewController.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 26 class ViewController: UIViewController { let colors: [UIColor] = [.systemRed, .systemBlue, .systemPink, .systemGreen, .systemIndigo] let percentages: [CGFloat] = [0.1, 0.2, 0.5, 0.3, 0.9] @IBOutlet weak var graphStackView: UIStackView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. graphStackView.distribution = .fillEqually graphStackView.alignment = .bottom graphStackView.spacing = 20 graphStackView.isLayoutMarginsRelativeArrangement = true graphStackView.layoutMargins.left = 20 graphStackView.layoutMargins.right = 20 for (color, percentage) in zip(colors, percentages) { addBar(bgColor: color, percentage: percentage) } } private func addBar(bgColor: UIColor, percentage: CGFloat ) { let view = UIView() let height = graphStackView.frame.height * percentage view.backgroundColor = bgColor view.heightAnchor.constraint(equalToConstant: height).isActive = true graphStackView.addArrangedSubview(view) } } こんなに短いコードで次のような棒グラフが表現できる。 ...

2019-12-24 · (updated 2019-12-26) · 4 min · 820 words

iPhoneアプリ開発メモ - 棒グラフの作成(UIKit/CoreGraphics) (2)

目標 前回の棒グラフを複数ページにする 今回は、1ページに最大5本の棒が並ぶ、とする。 ページは横スライドで切り替える プロジェクトは前回のものを引き継がず、新しく作る。 用いるデータ ViewController.swiftが次のようなデータを持っていることを想定する。 1 2 3 4 5 var dataSource = [ (7, "太郎"), (1, "次郎"), (2, "三郎"), (6, "四郎"), (3, "五郎"), (9, "六郎"), (2, "七郎"), (3, "八郎"), (1, "九郎"), (5, "十郎"), (1, "十一郎"), (1, "十二郎"), (6, "十三郎") ] このデータは、後で整形してグラフ描画用のデータに変換する。 UScrollViewの配置 Main.storyboardにUIScrollViewを配置する。サイズ設定はコードで行うので、ここでは単に配置するだけ。 その後、UIScrollViewのOutlet接続をViewController.swiftに対して行う。 UIScrollVieの設定 「スクロールの対象となるコンテンツを中に入れる」だけで、スクロール可能なViewが作れる。ただし、思い通りの表示にするためには、UIScrollViewやコンテンツのサイズを設定しておく必要がある。 複数ページを持つコンテンツを作りたいので、コンテンツ用Viewの中にページ用のViewが複数存在する状態になる。なのでページの位置やサイズもちゃんと設定する。 ScrollViewのレイアウト こんな感じにする。 なのでコードはこんな感じにする。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class ViewController: UIViewController { @IBOutlet weak var scrollView: UIScrollView! let marginX: CGFloat = 80 let marginY: CGFloat = 40 override func viewDidLoad() { super.viewDidLoad() scrollView.frame = CGRect( x: marginX, y: marginY, width: view.bounds.width - 2*marginX, height: view.bounds.height - 2*marginY ) } } contentsViewのレイアウト 例えばページが5枚だとすると、ページ5枚で1つのcontentsViewを作る。それを念頭においてレイアウトを組む必要がある。 ダミー用GraphView作成 GraphViewに関する位置、サイズの設定は、イニシャライザに任せるように書く。引数として、(ページ番号, スクロールビューのサイズ, データ)を与える。とりあえず今の所はデータを色として、これをbackgroundColorに設定する。 ...

2019-12-22 · (updated 2019-12-24) · 6 min · 1201 words

iPhoneアプリ開発メモ - 棒グラフの作成(UIKit/CoreGraphics) (1)

iPhoneアプリで棒グラフを描く方法として考えられるのは次の3通り。 ライブラリを使う - Chartsというライブラリがある。おそらくこれが一般的な選択肢。 Viewを棒グラフに見立てる - UIStackViewをうまく使った例にこんなのがある。 CoreGraphicsを使って手で描画する - 厳密にはUIKit側でこれを扱いやすくしたものがあるみたいで、Drawingとしてまとめられている。 ここではCoreGraphicsの練習を兼ねて、3つ目の方法で棒グラフを書いてみる。 目標 棒グラフを表示する 棒グラフの先端に値を表示する 今回用いるデータは(ラベル, 整数値)とする 整数値を棒グラフの高さとし、棒グラフの頂点にその数字を描く ラベルは棒グラフの下に描く 整数値が最も大きなものの色をオレンジ色にする 画面の大きさに関わらない図を作る 要するに以下のようなものを作る。 細かいレイアウト 画面の大きさに応じて棒グラフのサイズを変更する必要があるため、レイアウトについてそれなりに決めておく。テキストについては適当なサイズに設定する。 ビューの幅を$W$、barWidthを$w$、データの個数を$n$とする。するとpadXの個数は$n+1$である。このとから、$w$は次のように計算できる。 $$ \begin{aligned} & wn + \frac{w}{2}(n+1) = W \\ \Leftrightarrow&\ w = \frac{2W}{3n + 1} \end{aligned} $$ この計算結果をbarWidthとして利用する。 準備 Main.storyboardを次のようにしておく。 GraphViewのClassをGraphViewにする。新たにGraphView.swiftを作っておく。 GraphView.swiftを作る 以下が雛形。 1 2 3 4 5 6 7 8 9 10 class GraphView: UIView { override func awakeFromNib() { } let data: [(String, CGFloat)] = [(7, "太郎"), (1, "次郎"), (2, "三郎"), (6, "四郎"), (3, "五郎")] override func draw(_ rect: CGRect) { } } Viewの枠線 layer.borderWidthとlayer.borderColorで枠線を設定できる。これはCoreGraphicsとは関係ない。 ...

2019-12-21 · (updated 2019-12-22) · 4 min · 641 words

iPhoneアプリ開発メモ - UIPresentationControllerの利用

UIPresentationControllerを利用すると、モーダル表示の方法をカスタマイズできる。これについて備忘録を残す。 そもそもモーダル表示とは そもそもモーダル表示って何?と思ったので調べる。モーダルと検索すると「モーダルウインドウ」の話がよく出てくる。これは「ある操作を終えるまで親ウインドウの操作ができない子ウインドウ」という意味で使われているようだ。これはモーダル表示と似たような意味なのだろうか。判然としないので一次資料を漁る。 AppleのHuman Interface GuidelineにModalityの意味が書いてあって、これを引用すると、 Modality is a design technique that presents content in a temporary mode that’s separate from the user's previous current context and requires an explicit action to exit. [意訳] Modalityとは、ユーザの以前の文脈から離れた一時的なモードでコンテンツを表示するデザインの手法。そのモードを終了するためには何か明示的なアクションを必要とする。 ほとんど同じ意味っぽい。 例えば次のようなモーダル表示(Page Sheet)の場合,呼び出し元が下にあってその上に青いビューが載っている。ここでは、「上から下に引っ張る」というアクションを起こすことで、このビューを閉じることができる。 用意するもの 表示元のViewController 表示先のViewController UIPresentationControllerのサブクラス - これが表示先のViewControllerの表示方法を規定する。 ここでは、表示先のViewControllerのStoryboard IDをdestとする. 準備 まずはボタンをクリックすると表示されるものだけ作る。 Main.storyboard 表示元にはボタンを配置する。表示先はラベルを配置し、適切なConstraintを設定しておく。 ViewController.swift ボタンのAction接続を作る。ボタンがタップされたら遷移するようにする。 1 2 3 4 5 6 7 8 9 10 11 class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } @IBAction func buttonTapped(_ sender: Any) { let vc = (storyboard?.instantiateViewController(identifier: "dest"))! present(vc, animated: true, completion: nil) } } 遷移前の設定 buttonTappedに追記して次のようにする。 ...

2019-12-19 · (updated 2019-12-22) · 4 min · 718 words

Elm/JavaScript ローカルサーバーで通信する際にハマったこと

今回たまたまクライアント側でElmを使ったけど、これはElmに限ったことではない。 結論 Client側での留意点 urlはlocalhost:[port]ではなくhttp://localhost:[port]と指定しなければならない。つまり、URLにはちゃんとスキーム名を指定する。 Server側での留意点 Access-Control-Allow-Originに関連するヘッダーをちゃんと設定する。 成功コード プログラムの内容 サーバーは{ "msg" : "Hello, World!" }という内容のJSONを送ってくるので、クライアントはその値を受け取って"Success: Hello, World!“を出力する。それだけ。 Client: Elm 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 module Main exposing (..) import Browser exposing (..) import Json.Decode exposing (..) import Http exposing (..) import Html exposing (..) import Html.Attributes exposing (..) main = Browser.element { init = init , update = update , view = view , subscriptions = subscriptions } type Model = Loading | Failed | Success String init : () -> (Model, Cmd Msg) init _ = ( Loading, getServer ) type Msg = GotData (Result Http.Error String) update : Msg -> Model -> (Model, Cmd Msg) update msg model = case msg of GotData result -> case result of Ok str -> (Success str, Cmd.none) Err _ -> (Failed, Cmd.none) getServer : Cmd Msg getServer = Http.get { url = "http://localhost:3000" , expect = Http.expectJson GotData dataDecoder } dataDecoder : Decoder String dataDecoder = field "msg" string view : Model -> Html Msg view model = case model of Failed -> p [] [ text "Failed!" ] Loading -> p [] [ text "Loading..." ] Success str -> p [] [ text ("Success : " ++ str) ] subscriptions : Model -> Sub Msg subscriptions _ = Sub.none Server: JavaScript (Node.js) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const http = require('http'); const server = http.createServer(); server.on('request', (req, res) => { res.writeHead(200, { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' }); const body = { msg: 'Hello, World!' }; res.write(JSON.stringify(body)) res.end(); }); server.listen(3000); 失敗と解決までの流れ Http.getの引数 初めはサーバー側で次のようにしていた。 ...

2019-12-19 · (updated 2019-12-19) · 3 min · 602 words