yoshikit1996’s diary

日々勉強したことの備忘録です。

Scalaスケーラブルプログラミング読書メモ2

クラスパラメータ

クラス名の直後に書くコンストラクタを基本コンストラクタと呼び,基本コンストラクタの引数をクラスパラメータという. class Rational(n: Int, d: Int)

事前条件チェック

requireを使うと,引数に渡される値に制限を設けることができる.

class Rational(n: Int, d: Int){
 require(d != 0)
 override def toString = n + "/" + d
}

クラスにおけるvar, valの可視性

class Num(n: Int) // private
class Num(val n: Int) // public, valなのでイミュータブル
class Num(var n: Int) // public, varなのでミュータブル

補助コンストラクター

class Rational(n: Int, d: Int){
  def this(n: Int) = this(n, 1)
}

リテラル識別子

識別子とは,変数名・関数名・クラス名などに用いられるもの.リテラル識別子とは,バッククォートで囲まれた任意の文字列.リテラル識別子の考え方は,バッククォートにどんな文字列をいれてもランタイムに識別を受付させようというもの.

case class `Rational`(n: Int, d: Int){
 require(d != 0)
 override def toString = n + "/" + d
}

val r = `Rational`(3, 4)
println(r.`toString`)

for式

// yield
val evens = for(n <- (1 to 10) if n % 2 == 0) yield n
println(evens)
// フィルタリング
for(n <- (1 to 10) if n % 2 == 0){
    println(n)
}

// 複数フィルター
for(n <- (1 to 10)
if n % 2 == 0
if 3 < n
)println(n)

// 入れ子,中間結果の束縛
for(
file <- filesHere
if file.getName.endsWith(".scala");
line <- fileLines(file)
trimmed = line.trim
if trimmed.matches(pattern)
) println(file + ": " + trimmed)

関数のプレースホルダー構文

// コンパイラーが十分な型情報を持っていない時の対策
val f = (_: Int) + (_: Int)
println(f(3, 4))

その他

  • Scalaの定数はUpperCamelCase
  • 暗黙の型変換 `implicit def intToRational(x: Int) = Rational(x)
  • スコープの内側の変数を外側の変数を見えなくすることをシャドウイングという
  • 副作用があるようなメソッドはメソッド名に空括弧をつける習慣がある.def width(): Int
  • 名前渡しパラメータ.myAssert(predicate: => Boolean)という関数を定義してmyAssert(3 < 5)とした場合,3 < 5は関数呼び出し後に評価される.

Scalaの型について(Scalaスケーラブルプログラミング読書メモ1)

型が重要な感じがしたのでメモ.

静的な型づけ

静的型付け言語であるScalaでは,ジェネリクスで型をパラメータ化したり,抽象型で型の詳細を隠蔽することができる.型が動的な言語(PythonRubyとか)では,ジェネリクスや抽象型などの仕組みはなくとも,上記のようなことができる.そのため,Scalaジェネリクスや抽象型を使用することは煩わしく感じられることもあるが,静的な型には様々なメリットが存在する.

静的な型付け言語では,プログラム実行より前に型が決まっている.プログラム実行前に型に関する情報がわかるため,次のようなメリットをもたらす.

検証可能性

静的な型システムはある種のランタイムエラーが存在しないことを証明できる.

例えば,次のようなコードの場合,コンパイル時にエラーが判明する.(IDEであれば,赤線を引いてくれる)

val convertToString = (num: Int) => num.toString()
println(convertToString(true))

他にもプライベートなメソッドに外部からアクセスしていないかどうかや,関数に渡した引数の数が正しいかどうか等がプログラム実行前にわかる. (あからさまなエラーだと,動的型言語でもIDEが検出してくれるが,少し複雑なプログラムになると,検出できないはず)

一方で,動的な型付け言語では,プログラム実行前に検証しづらいため,しっかりテストを書く習慣があるらしいが, 最初から検証しやすい静的型付け言語を使えばよい気がする.

リファクタリング安全性

静的な言語で書かれたプログラムをリファクタリングする際,コンパイルが通るかどうかを確認することにより,変更内容が正しいかどうか検証することができる. (コンパイルが通ったからといって,プログラムにバグがないことを証明できるわけではないが...)

IDEのアシスト

静的型付け言語で書かれたプログラムをIDEで変数名やメソッド名を補完したり,リファクタリングをしたりする際,IDEのアシストが強力である.

新しい型を作れる

Scalaのクラスでは*-などの演算子を実装できる.そのため,ネイティブサポートがあるかのように感じられる.

高水準な型システム

Javaで大文字が文字列に含まれているかどうかをチェックする際,ループで文字を1文字ずつ処理する必要があり,低水準なことを考慮しながらプログラムを書く必要がある.

// Java
boolean nameHasUpperCase = false;
for(int i = 0; i < name; ++i){
  if(Character.isUpperCase(name.charAt(i))){
    nameHasUpperCase = true;
    break;
  }
}

対して,Scalaでは述語関数(条件を満たした場合に真、または偽を返す関数)で高水準にコードを書くことができる.ここでnameは紛れもなくJavaのStringクラスだが,必要に応じてStringOpsにラップ(暗黙変換)される.

// Scala
val nameHasUpperCase = name.exists(_.isUpper)

この例では,本来低水準なもの(JavaのStringクラス)を高水準なもの(String + StringOps)として扱うことができ,さらにそれを組み込み型であるかのように扱えている.

コンパニオンオブジェクト

Scalaでオブジェクトを定義して,関数のようにオブジェクト名()と呼び出すとapplyメソッドが自動的に呼び出される.

object Person{
    def apply(name: String) {
        println(name)
    }
}

Person("ほげほげ") // "ほげほげ"と標準出力

これを利用すると,クライアントからnew演算子を使わずにPersonをインスタンス化することができる.

class Person(val name: String)

object Person{
    def apply(name: String): Person = {
        new Person(name)
    }
}

val p = Person("ふがふが")
println(p.name) // "ふがふが"

ケースクラスならコンパニオンオブジェクトなしでインスタンス化できる.

case class Person(name: String)

val p = Person("ふがふが")
println(p.name) // "ふがふが"

Angulerのメモ

ファイル

ファイル名 説明
app.component.ts アプリで最初に呼び出されるコンポーネント.ルートコンポーネント
app.module.ts 起動時に呼び出されるモジュール.Angulerの構成要素をまとめる器のようなもの.
index.html 最初のメインページ

データバインディング

データバインディングの種類 記法
補完 [proerty] = "value"
プロパティバインディング {{...}}
イベントバインディング {event} = "handler"
双方向バインディング [(target)] = "value"
属性バインディング [attr.name] = "exp"
クラスバインディング [class.name] = "exp"
スタイルバインディング [style.name] = "exp"

データバインディングの例

// プロパティバインディング
@Component({
  selector: 'my-app',
  template: '<img [src]="image" />'
})

export class AppComponent{
  image = "https://www.google.co.jp/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"
}
<!-- 単位付きのスタイルプロパティ -->
<!-- パーセントでフォントサイズを指定する例 -->
<div [style.font-size.%]="size">hoge</div>
<!-- イベントバインディング -->
<input type="button" (click)="show()" value="foo" /> // クリック時
<input type="button" (mouseenter)="show()" value="bar" /> // マウスポインターが要素に入った時
<input type="button" (input)="show($event)" value="foobar" /> // 入力内容が変更された時
// テンプレート参照変数
@Component({
  selector: 'my-app',
  template: `
    <input #txt id="txt" name="txt" type="text" (input)="show(txt.value)" />
    <ul [innerHTML]="msg"></ul>
  `
})

export class AppComponent {
  ...
  show(input: string){
    this.msg = += `<li>${input}</li>`
  }
}
// テンプレート参照変数
template: `
  <input #last type="text" (change)="foo()" />
  <input #first type="text" (change)="bar()" />
  <div>{{last.value}}{{first.value}}</div> // テンプレート内の他の要素から参照
`
<!-- キーイベントのフィルタリング -->
<!-- `Enter`キーが押された時にだけイベントを発生させる -->
<input ... (keyup.enter)="show($event)" />
<!-- 双方向バインディング -->
<!-- ngModelディレクティブ -->
<input id="name" name="name" type="text" [ngModel]="myName"  />

「?.」演算子

<!-- `member`が空であるかどうかを確認し,空でない場合,nameにアクセス -->
<div>{{member?.name}}</div>

パイプ

パイプとはテンプレート上に埋め込まれたデータを加工/整形するための仕組み. パイプには次のようなものが存在する.

  • lowercase
  • uppercase
  • titlecase
  • slice
  • date
  • number
  • percent
  • json
  • i18nPlural
  • i18nSelect
  • async

<!-- `price`は式,`currency`はパイプ,`'JPY'`はパイプのパラメータ. -->
{{price | currency: 'JPY'}}

ディレクティブ

ディレクティブ 説明
ngIf if文
ngSwitch switch文
ngFor for文
ngTemplateOutlet 用意されたテンプレートの内容をインポート
ngComponentOutlet 用意されたコンポーネントの内容をインポート
ngStyle 要素にスタイルプロパティを付与
ngClass クラスを付与
ngPlural 数値に応じて出力を切り替える.

ディレクティブの例

<!-- ディレクティブ名のプリフィックスに`*`が来ることに注意 -->
<div *ngif="show">
  <p>hoge hoge</p>
</div>
<!-- `*ngStyle`ではなくて`[ngStyle]` -->
<div> [ngStyle]="styles"> ... </div>

...

export class AppComponent{
  ...
  // 動的にスタイルを付与することが可能
  get styles() {
    return {
      'background-color': this.back ? '#f00' : '',
      'color': this.fore ? '#fff' : '#000',
    }
  }
}

フォーム

テンプレート駆動型のフォーム

  • 検証属性が指定できる
    • required
    • minlength
    • maxlength
    • email
  • 検証エラーかあるかどうかをチェック
    • フォーム名.invalid

フォームの状態検知

// 検証項目単位でエラーの有無をチェック
入力要素名.erros.検証型 // 検証型には`required`, `minlength`...などが当てはまる

入力要素名.valid
入力要素名.invalid
フォーム名.valid
フォーム名.invalid

.pristine // 変更されていない
.dirty // 更新された
.touched // フォーカスが当たった
.untouched // 1度もフォーカスが当たっていない

モデル駆動型のフォーム

  1. app.module.tsReactiveFormsModuleを有効にする.
  2. templateに[formGroup][formControl]`を記述する.
  3. コンポーネントのクラスでFormGroupオブジェクトとFormControlオブジェクトを生成する.

その他メモ

  • (ngModelChange)="exp"でデータバインディング時の入力値を加工する
  • イベント発生時のマウス/キー情報を取得する
  • イベントのバブリングをキャンセルする

React Nativeのメモ

アプリの初期化

# アプリの雛形を作成
expo init SampleApp

# アプリを起動
cd SampleApp
expo start

# テストの追加
yarn add --dev jest

# Redux
yarn add redux
yarn add react-redux

# 画面遷移
yarn add react-navigation

# flow
yarn add --dev flow-bin@0.61.0 babel-preset-flow

package.json

"license": "UNLICENSED",
"scripts": {
    ...
    "flow": "flow",
    "flow-stop": "flow stop”
}

.flowconfig

[ignore]
<PROJECT_ROOT>/node_modules/.*
<PROJECT_ROOT>/libdefs.js
[include]
[libs]
./libdefs.js
[options]
all=true

var, let, constによる変数宣言

varは関数スコープ
letはブロックスコープ
constはブロックスコープでイミュータブル

コンポーネントのkey

コンポーネントレンダリングされる際、コンポーネントはkeyという属性を保持している。 したがって、次のようにkeyを指定せずにコンポーネントレンダリングしようとすると、warningが発生する。

<View>
  { ["1", "2", "3"].map((item) => {
    return (<Text>{"アイテム: " + item}</Text>) // warning
  })}
</View>

keyの指定にはkeyExtractorという専用の属性が存在し、keyの指定にはこれを用いることができる。

keyExtractor={(item, index) => "hoge_" + item.index}

React Nativeにaws-amplifyをインポートしたらSyntaxErrorでハマった

create-react-native-appでアプリを作成し、aws-amplifyをインポートし、テストすると、下記のようなエラーが発生します。

import Amplify from 'aws-amplify';
import aws_exports from './aws-exports';
  ● Test suite failed to run

    /???/???/node_modules/aws-amplify-react-native/dist/index.js:14
    import { default as AmplifyCore, I18n } from 'aws-amplify';
    ^^^^^^

    SyntaxError: Unexpected token import

    > 1 | import React from 'react';
      2 | import { StyleSheet, Text, View } from 'react-native';
      3 | import Amplify, { withAuthenticator } from 'aws-amplify-react-native';

      at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/script_transformer.js:316:17)
      at Object.<anonymous> (App.js:1:884)||<

原因はnode_modules/aws-amplify-react-native/dist/index.js:14がトランスパイルされていないことです。
なので、トランスパイルするようにpackage.jsonに次のような記述をするとテストが通ります。

   "jest": {
    "preset": "jest-expo",
    "transformIgnorePatterns": []
   }

ただ、この設定だと余計なファイルも含めてすべてトランスパイルされるのでテストに時間がかかります。
なのでyarn testして1回トランスパイルしたあと、transformIgnorePatternsを削除するとテストが速く動きます。

   "jest": {
    "preset": "jest-expo",
   }