Elmメモ ドラッグ移動の実現(2) - elm-draggableの利用

前回はBrowsertやSvgなどの標準的なパッケージを利用してドラッグ機能を実現した。今回はelm-draggableというパッケージを使ってドラッグ機能を実現してみる。 準備 Elmのプロジェクトを作成して、src/Main.elmとsrc/Circle.elmを作成。 Circle.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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 module Circle exposing (..) type alias Id = Int type alias Circle = { id : Id , x : Float , y : Float , r : Float } type alias Circles = { all : List Circle , nextId : Id } empty : Circles empty = { all = [] , nextId = 0 } type alias CircleNoId = { x : Float , y : Float , r : Float } add : CircleNoId -> Circles -> Circles add c circles = let circle = { id = circles.nextId , x = c.x , y = c.y , r = c.r } in { circles | all = circle :: circles.all , nextId = circles.nextId + 1 } fromList : List CircleNoId -> Circles fromList list = { all = List.indexedMap (\i c -> { id = i, x = c.x, y = c.y, r = c.r }) list , nextId = List.length list } toList : Circles -> List Circle toList circles = circles.all update : Id -> (Circle -> Circle) -> Circles -> Circles update id f circles = let new = List.foldr (\c acc -> if c.id == id then f c :: acc else c :: acc ) [] circles.all in { circles | all = new } Main.elm Circlesを描画するところまで書く。 ...

2020-02-27 · (updated 2020-02-27) · 6 min · 1148 words

Elmメモ ドラッグ移動の実現(1)

ElmでSVGの要素をドラッグ移動したいと思った。ドラッグ操作を実現するパッケージにelm-draggableがある。今回は勉強として、それに頼らず実装することを試みる。elm-draggableを用いた方法については次回やる。 初期状態 詳細は省略するが、Elmプロジェクトを作成してelm/svgとelm/jsonをインストールしておく。 src/Main.elmは以下のようにしておく。elm reactorで動くことを確認する。 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 module Main exposing (..) import Browser import Browser.Events as BE import Html exposing (..) import Html.Attributes exposing (..) import Json.Decode as JD import Svg as S exposing (Svg) import Svg.Attributes as SA import Svg.Events as SE main = Browser.element { init = init , update = update , view = view , subscriptions = subscriptions } type alias Model = {} type Msg = Dummy init : () -> ( Model, Cmd Msg ) init _ = ( {}, Cmd.none ) update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = ( model, Cmd.none ) view : Model -> Html Msg view model = div [] [] subscriptions : Model -> Sub Msg subscriptions model = Sub.none 目標 SVGの領域に円が複数存在する 円をドラッグ移動できるようにしたい。 円のドラッグ中はその色を橙色にし、それ以外のときは白にする 方針 ドラッグ処理については次の処理で実現することになる。 ...

2020-02-25 · (updated 2020-03-04) · 8 min · 1588 words

Elmメモ - 画像のプレビュー機能を作る

Elmを利用して、画像を選択してそれを表示するアプリを作る。 ファイル読み込みの方法 Select.file関数を利用する。これはファイル選択用のエクスプローラを開くためのCmd Msgを作成してくれる。選択したファイルはMsgに載せられる。 適切なMIMEタイプを指定すると、エクスプローラ上にてそのタイプのファイルしか選択できなくなる。例えば、text/plainを選択しておけば、拡張子.txtのファイルしか選択できなくなる。 1 Select.file "MIMEタイプのリスト" "Msg" 画像ファイルへの変換 こうして得られたファイルはFileと呼ばれる型で保持される。 もしファイルを文字列として扱いたいなら、File.toStringを利用する。 もし画像として扱いたいなら、File.toUrlを利用する。これは画像をBase64符号化した文字列を作る。これをimgタグのsrc属性に指定すれば、画像が表示される。 画像を選択し、それを表示するアプリの作成 準備 プロジェクトを作成して、elm/fileをインストール。 $ elm init $ elm install elm/file src/Main.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 module Main exposing (..) import Browser import Html exposing (..) import Html.Attributes exposing (..) import Html.Events exposing (..) import File exposing (File) import File.Select as Select import Task main = Browser.element { init = init , update = update , view = view , subscriptions = subscriptions } type alias Model = { } init : () -> (Model, Cmd Msg) init _ = ( { } , Cmd.none ) type Msg = Msg update : Msg -> Model -> (Model, Cmd Msg) update msg model = ( model , Cmd.none ) view : Model -> Html Msg view model = div [] [ ] subscriptions : Model -> Sub Msg subscriptions model = Sub.none htmlファイルを自分で作りたいので、makeのときはjsファイルを単独で生成させる。 ...

2020-01-13 · (updated 2020-01-13) · 4 min · 699 words

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

準備 前回のsrc/IPAddr.elmを全て消し、内容を以下の通りにする。 1 2 3 4 5 module IPAddr exposing (..) import Parser type IPAddr = IPAddr Int Int Int Int $ elm repl > import Parser exposing (..) > import IPAddr exposing (..) Parserの基本 以下の2つのステップに分かれる。 Parserを作る Parserを実行する - Parser.runを用いる ライブラリでは、標準で用意されているParserと、それらを組み合わせて新たなParserを作るための関数が用意されている。 > run int "123" Ok 123 : Result (List Parser.DeadEnd) Int > run int "123abc" Ok 123 : Result (List Parser.DeadEnd) Int > run int "abc123abc" Err [{ col = 1, problem = ExpectingInt, row = 1 }] : Result (List Parser.DeadEnd) Int succeed 何もパースせず、決まった結果だけを返すパーサー。 > run (succeed "Hello") "123abcde" Ok "Hello" : Result (List Parser.DeadEnd) String パーサーを組み合わせるときの基本になる。 ...

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

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

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