Direct Manipulationに、パーサコンビネータでジェスチャーを実装した話
by Nobkz
@Nobkz
- はなだ のぶかず
- 株式会社Groovenauts Rockstar Engineer
- Scala/Shen/Rust/Erlangなど
- ボードゲーム/デジタルゲーム/ika2
- もともと音響屋
- 音響プログラミング言語を作成したりしてた
この登壇について
- この登壇はDirect ManipulationのScala実装に関する登壇です。
- パーサコンビネータが中心になります
- ジェスチャーが楽しく実装できるようになるといいな!?
Direct Manipulation[Shneiderman, 1983]
Shneidermanによって提唱された対象が直感的、身体的に操作でき、すぐに結果がフィードバックされるインタラクションのスタイル
DMである例、そうでない例
Visual Programming Language
- FLOWer
- Viscuit
DM + Code & Code + DM
- Sketch-n-Sketch
- SigHex
ジェスチャーについて
ジェスチャーの重要性
- ボタンを減らす
- うまくインタラクションデザインすると使いやすくなる
- タッチUIではホントに重要
- Direct Manipulationに貢献する
ジェスチャーの欠点
- デザインが難しい
- 実装が難しい
- 見えづらいので、デバッグしにくい
ジェスチャーの種類
- タップ、 ダブルタップ
- ピンチイン、 ピンチアウト
- フリック
- ホールド
- etc ...
ジェスチャーの可能性
- タップとダブルタップ、フリック、ホルード、スクロール
- ピンチとスクロール
- どう区別するか?
イベントを見わける
- 複数の可能性をチェックする
- DFA (決定性有限オートマトン)
- NFA (非決定性有限オートマトン)
- パーサの文字列の解釈とジェスチャーのイベント解釈は似ている
- パーサコンビネータで楽に書けるのでは?
パーサコンビネータ
パーサ同士を組み合わせて新しいパーサをつくる技法。
パーサ
Parser
"(10 20 30)"
List(10,20,30)
入力文字列
出力データ
Scalaで書くと...
abstract class Parser[+A] extends
(String => A)
Parser
Parser
"(10 20 30) (.... "
List(10,20,30)
+
"(..."
入力文字列
出力データ
+
続きの文字列
Scalaで書くと...
abstract class Parser[+A] extends
(String => (A, String))
エラーが出るかもしれない
"(10, 20, "
Error!!!
Parser
"(10 20 30) (.... "
List(10,20,30)
+
"(..."
Scalaで書くと
abstract class Parser[+A] extends String => Result[A]
abstract class Result[+A]
case class Success[+A](item:A,next:String) extends Result[A]
case class Error(msg:String) extends Result[Nothing]
パーサのmap
"(10, 20, "
Error!!!
Parser
"(10 20 30) (.... "
List(10,20,30)
+
"(..."
sum
60
f
パーサのmap
"(10, 20, "
Error!!!
Parser
"(10 20 30) (.... "
List(10,20,30)
+
"(..."
sum
60
+
"(.."
f
パーサのmap
Parser
sum
f
map
=
New Parser!!
Scalaで書くと...
abstract class Parser[+A] extends (String => Result[A]) { p =>
def map[B](f: A => B) : Parser[B] = new Parser[B]{
def apply(input: Text) : Result[B] =
p(input) match {
case Success(a, next) => Success(f(a), next)
case Error(msg) => Error(msg)
}
}
}
parserA map (x => x + 1)
パーサのflatMap
Parser
flatMap
f
A
Parser
パーサのflatMap
Parser1
List(10,20,30)
+
"(..."
f
Parser2
Scalaで書くと...
abstract class Parser[+A] extends (String => Result[A]) { p =>
def flatMap[B](f: A => Parser[B]) : Parser[B] = new Parser[B]{
def apply(input: Text) : Result[B] =
p(input) match {
case Success(a, next) =>
val newParser = f(a)
newParser(next)
case Error(msg) => Error(msg)
}
}
}
パーサを逐次的に組み合わせる
Parser1
"(10 20 30)
concat (.... "
List(10,20,30)
+
"concat (..."
Parser2
"concat (..."
Concat
+
"(..."
Parser
"(10 20 30)
concat (.... "
List(10,20,30)
+
"concat (..."
Concat
+
"(..."
パーサを組み合わせる
Parser
(List(10,20,30)
+
Concat)
+
"(..."
Scalaで書くと..?
abstract class Parser[+A] extends (String => Result[+A]) { p1 =>
def ~[B](p2: Parser[B]) : Parser = new Parser[(A,B)] {
val apply(input: String) => Result[(A,B)] = {
val p3 =
for{ a <- p1
b <- p2
} yield (a,b)
p3(input)
}
}
}
足し算のパーサ
case class Add(x: Double, y: Double)
val double : Parser[Double] = ...
val plus : Parser[String] = ....
val add : Parser[Add] = (double ~ plus ~ double) map { ((x, _), y) => Add(x,y) }
ジェスチャーとパーサ
Parser
"(10 20 30)"
List(10,20,30)
入力文字列
出力データ
ジェスチャーとパーサ
Parser
List(10,20,30)
Seq[A]
出力データ
出力列
ジェスチャーとパーサ
Parser
MoveUp
Seq[(Double,
Double)]
出力データ
出力列
Scalaで書くと
abstract class Parser[Input,+T] extends (Input=>Result[Input, T])
abstract class Result[Input, +T]
case class Success[Input, +T] (t: T, input: Input) extends Result[Input, +T]
case class Error[Input](msg: String) extends Result[Input, Nothing]
case class Vector2(x:Double, y:Double)
class Gesture[+T] extends Parser[Vector2, T]
ジェスチャーを定義してみよう
- まずは、Inputの型の定義
- 出力する型の定義
- scala-parser-combinatorsを利用
入力シーケンスの要素の定義
case class Vector2(x: Double, y: Double) {
def +(o: Vector2) = Vector2(x + o.x, y + o.y)
def -(o: Vector2) = Vector2(x - o.x, y - o.y)
def *(f: Double) = Vector2(x * f, y * f)
def *(o: Vector2) = x * o.x + y * o.y
def /(f: Double) = this * (1 / f)
def magnitude = math.sqrt(x * x + y * y)
def normalize = this / magnitude
def angle = math.atan2(y, x)
}
scala-parser-combinatorsのParserの定義
trait Parser {
type Elem
type Input = Reader[Elem]
...
abstract class Parser[+T] extends (Input => ParseResult[T])
....
}
scala-parser-combinatorsのReader, Positionの定義
abstract class Reader[+T] {
def atEnd: Boolean
def first: T
def pos: Position
def rest: Reader[T]
....
}
trait Position {
def line: Int
def column: Int
....
}
GestureReader,GesturePositionの定義
class GestureReader( val data: List[Vector2], override val offset: Int ) extends Reader[Vector2] {
def this( data: List[Vector2] ) = this( data, 0 )
class GesturePosition( val offset: Int ) extends Position
{
override val line = 1
override def column = offset + 1
override def lineContents: String = ""
}
override def atEnd = offset >= (data.length - 1)
override def first = data( offset )
override def pos = new GesturePosition( offset )
override def rest = new GestureReader( data, offset + 1 )
}
Gestureの出力の型を定義
object Gesture{
trait Atom {
val pos : Vector2
}
object Atom {
case class Idle(pos:Vector2) extends Atom
case class Up(startPos:Vector2, pos:Vector2) extends Atom
case class Down (startPos:Vector2, pos:Vector2) extends Atom
case class Left (startPos:Vector2, pos:Vector2) extends Atom
case class Right (startPos:Vector2, pos:Vector2) extends Atom
}
}
case class Gesture(last:Gesture.Atom, history:List[Gesture.Atom])
ジェスチャー履歴の更新
case class Gesture(last:Gesture.Atom, history:List[Gesture.Atom])
- Gesture型には、historyがある
- Atomをパースするたびに,histroyを更新する。
(currentGesture:Gesture)=>(atom:Atom)=>Gesture
ジェスチャーの実装
val pos : Parser[Vector2] = elem("POS", e => true)
def gesture(g:Gesture) : Parser[Gesture] =
atom(g).flatMap(gesture) | atom(g)
def atom(g:Gesture) = up(g) | down(g) | left(g) | right(g) | idle
def idle(g:Gesture) : Parser[Gesture] =
(pos ^^ { p => (p,g)}).filter { ... }
def move(g:Gesture) : Parser[Move] = { .. }
def up(g:Gesture) : Parser[Gesture] = ...
def down(g:Gesture) : Parser[Gesture] = ..
def left(g:Gesture) : Parser[Gesture] = ...
def right(g:Gesture): Parser[Gesture] = ...
さらに高度なジェスチャーへ
- up, down, left rightのみで構成している
- historyを更にパースする
- 時間管理
- タップとダブルタップの解釈
- 複数の指の解釈
ジェスチャーと
パーサコンビネータ再考
- なにかに似ている..?
- MVCのC
- Rx系との比較(時間がなさそう)