Charaken 技術系ブログ

技術系に関して学んだことや、気になったことを記事にしていく。

【CSS】 忙しい人のためのSMACS

f:id:charaken:20200304232945p:plain

SMACSS (Scalable and Modular Architecture for CSS; スマックス) の概要についてご紹介致します。



SMACSS概要

CSSを5つにカテゴライズした構造

カテゴリ 概要
Base Reset CSSなどのスタイルの基本
Layout ページのセクション(header, footer, content)
Module 再利用可能なモジュールパーツ
State Layout, Moduleの特定状態下でのスタイル
Theme Layout, Module, Stateの見え方 *1

各カテゴリの詳細

対象セレクタ

カテゴリ 対象セレクタ
Base Element selector ( div , a )
Descendant selector ( ul li )
Child selector ( ul>li )
Pseudo-classes( button:hover )
Layout ID
Class
Module Class
State Class
Theme Class

記法

カテゴリ 記法
Base -
Layout ID: 的確な命名 *2 、Class: プレフィックス l-
Module -
State Module名を含めることを推奨
Theme プレフィックス theme- *3

カテゴリ
Base div { margin: 0; }
Layout #header { margin: 0; }
.l-footer { margin: 0; }
Module .module { font-size: 1rem; }
.module-submodule { font-size: 1.2rem; }
.module.module-submodule { font-size: 1.4rem; }
State .is-module-active { color: #f00; }
Theme .theme-module { color: #777; }

!important の利用可否

カテゴリ ファイル
Base 不可
Layout 非推奨
Module 非推奨
State 使用可
Theme 非推奨

SCSSの利用時

ファイル構成

カテゴリ ファイル
Base base.scss*4
Layout layout/xxx.scss*5
Module module/xxx.scss*6
State states.scss*7
Theme -
+-layout/
| +-grid.scss
| +-...
+-module/
| +-callout.scss
| +-btn.scss
| +-...
+-base.scss
+-states.scss
+-site-settings.scss
+-mixins.scss

基本ファイルでの @import 内容

@import
  "site-settings",
  "mixins",
  "base",
  "states",
  "layout/grid",
  "module/callout";

まとめ

  • SMACSS は CSS を5つにカテゴライズした構造
    • Base, Layout, Module, State, Theme

最後に

忙しい人向けに、ざっくりとSMACSSについてまとめました。 細かい部分については注記を参照して下さい。

SMACSSは、2011年にJonathan Snookさんによって提唱されたArchitectureで、OOCSS (Object Oriented) や CSS Wrokflow などの影響を受けています。 また、BEMやFLOCSS、ECSSなどとの比較にも出されることが多く、おさえておいた方が良いアーキテクチャではあります。

公式ドキュメントには、カテゴリ毎の注意点やSCSSでの注意点、どうしてもBaseで避けられない事象についても記載があるため、 公式ドキュメントを読んでいただいた後に再度戻ってきていただくと、思い出せる手助けになると思います。


References


*1:Themeカテゴリはユーザーによって表示変更をする場合などに利用しますが、基本的には1つの表示形式になるため殆どの場合LayoutやModule、Stateで完結します。

*2:LayoutのプレフィックスをIDでも利用しても良いですが、ID利用が明確であるため必要性は薄いです。

*3:Themeのプレフィックスは複数の複雑な命名の場合のみ必要ですが、複雑でなければ不要です。

*4:SCSSでBaseを記述する場合、全て1つのファイルにする必要があります。

*5:SCSSでLayoutを記述する場合、1ファイルのみか、主なレイアウト毎にファイルを作成します。

*6:SCSSでModuleを記述する場合、各々のModuleでファイルを作成します。

*7:SCSSでStateを記述する場合、グローバルで利用するStateは専用のファイルに記述します。また、LayoutやModuleに関連するメディアクエリを含む場合は、Moduleのファイルに記述します。

【Rust】がばがばRust独学 - 13. Functional Language Features - 1 Closures (2)

f:id:charaken:20191223210559p:plain

Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)

doc.rust-lang.org

今回も、Functional Language FeaturesのClosuresについて学びつつ、気になった点をコード確認した結果を記事にしています。

  • versions
$ rustc --version
rustc 1.40.0 (73528e339 2019-12-16)


Closures: Anonymous Functions that Can Capture Their Environment

Closures: Anonymous Functions that Can Capture Their Environment - The Rust Programming Language

github.com

Closure Type Inference and Annotation

下記のClosureでは、パラメータに対する型や戻り値に対する型を利用していません。

let expensive_closure = |num| {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
    num
};

下記のように、型をつけることも可能です。

let expensive_closure = |num: u32| -> u32 {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
    num
};

しかし、通常はClosure自体は短く、狭いコンテキスト内で使用され、かつ関数内のクローズドな匿名関数であるため、型を推測できます。

そのため、型をつける冗長性を無くすために、一般的に型を記述しません。明示性・明確性を高めたい場合は型をつけてよいと記載されていました。

Fn について

昨日の記事にも出てきた Cacher 内に使用されている Fn についてです。

charaken.hatenablog.com

struct Cacher<T>
    where T: Fn(u32) -> u32
{
    calculation: T,
    value: Option<u32>,
}

クロージャーの型として Fn には3つ利用することが出来ます。

  • FnOnce : Closureが変数の所有権を取得
  • FnMut : 可変な値のOwnerを借用
  • Fn : 不変な値のOwnerを借用

ドキュメントを読んでも分からない・・・

ってことで、まずは定義を確認する。

pub trait Fn<Args> : FnMut<Args> {
    /// Performs the call operation.
    #[unstable(feature = "fn_traits", issue = "29625")]
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}

pub trait FnMut<Args> : FnOnce<Args> {
    /// Performs the call operation.
    #[unstable(feature = "fn_traits", issue = "29625")]
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}

pub trait FnOnce<Args> {
    /// The returned type after the call operator is used.
    #[stable(feature = "fn_once_output", since = "1.12.0")]
    type Output;

    /// Performs the call operation.
    #[unstable(feature = "fn_traits", issue = "29625")]
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

違いとしては、下記になります。

  • FnOnceself
  • FnMut&mut self
  • Fn&self

上記から推測すると、 FnOnce はClosureをそのまま利用するため1回利用・・・? Fn はアドレス参照なので複数回利用?そして、 FnMut はClosureを変更可能・・・?という風に読み取れますが、ちょっとわからないので、別記事に・・・


まとめ

  • Closureには型定義を一般的には実施しない
    • 明示性・明確性を高めたい場合は必要
  • FnOnceFnMutFn まじでわからん

最後に

Fn 系は今までに触ったことのない(もしくはよしなに頑張ってもらっていた?)概念なので正直分からない・・・

とりあえず、先に進めるために、次はIteratorsについて深めます。

【Rust】がばがばRust独学 - 13. Functional Language Features - 1 Closures (1)

f:id:charaken:20191223210559p:plain

Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)

doc.rust-lang.org

今回は、Functional Language FeaturesのClosuresについて学びつつ、気になった点をコード確認した結果を記事にしています。

  • versions
$ rustc --version
rustc 1.40.0 (73528e339 2019-12-16)


Functional Language Features: Iterators and Closures

Functional Language Features: Iterators and Closures - The Rust Programming Language

一般的な関数型言語の機能に似たRustの機能としては、下記をカバーしています。

  • Closures : 変数に格納できる関数のような構造
  • Iterators : 一連の要素を処理する方法

また、下記も記載されているそうです。

  • ClosuresIterators を利用したI/Oの改善方法
  • ClosuresIterators のパフォーマンス

Closures: Anonymous Functions that Can Capture Their Environment

Rustの Closures は下記が可能な匿名関数です。

  • 変数への保存
  • 引数として他関数へ渡せる

下記に、公式ドキュメント(Closures: Anonymous Functions that Can Capture Their Environment - The Rust Programming Language)のデモコードを元に、クロージャー利用までの手順を記載致します。

何も考慮せずに書いた場合

simulated_expensive_calculation 関数で約2秒停止するため、 generate_workoutintensity < 25 の場合、約4秒の待機時間が発生します。

use std::thread;
use std::time::Duration;

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value, simulated_random_number);
}

///
/// 実行に約2秒かかる仮想関数
///
fn simulated_expensive_calculation(intensity: u32) -> u32 {
    println!("calculating slowly...");
    let sec: u64 = 2;
    let duration: Duration = Duration::from_secs(sec);
    thread::sleep(duration);
    intensity
}

///
/// トレーニング計画を出力する関数
///
fn generate_workout(intensity: u32, random_number: u32) {
    if intensity < 25 {
        println!(
            "Today, do {} pushups!",
            simulated_expensive_calculation(intensity)
        );
        println!(
            "Next, do {} situps!",
            simulated_expensive_calculation(intensity)
        );
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                simulated_expensive_calculation(intensity)
            );
        }
    }
}

変数の利用

expensive_result の変数に代入することにより、intensity < 25 の場合でも、約2秒の待機時間になります。

しかし、下記の場合、intensity >= 25 && random_number == 3 の場合であっても待機時間が発生してしまいます。

use std::thread;
use std::time::Duration;

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value, simulated_random_number);
}

///
/// 実行に約2秒かかる仮想関数
///
fn simulated_expensive_calculation(intensity: u32) -> u32 {
    println!("calculating slowly...");
    let sec: u64 = 2;
    let duration: Duration = Duration::from_secs(sec);
    thread::sleep(duration);
    intensity
}

///
/// トレーニング計画を出力する関数
///
fn generate_workout(intensity: u32, random_number: u32) {
    let expensive_result = simulated_expensive_calculation(intensity);

    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_result);
        println!("Next, do {} situps!", expensive_result);
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!("Today, run for {} minutes!", expensive_result);
        }
    }
}

Closureの利用

無名関数を expensive_closure に代入することで、Closureの宣言が可能です。

ただ、下記のままでは、通常で書いた内容と変わりがありません。

use std::thread;
use std::time::Duration;

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value, simulated_random_number);
}

///
/// トレーニング計画を出力する関数
///
fn generate_workout(intensity: u32, random_number: u32) {
    // 実行に約2秒かかる仮想クロージャー
    let expensive_closure = |num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    };

    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_closure(intensity));
        println!("Next, do {} situps!", expensive_closure(intensity));
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!("Today, run for {} minutes!", expensive_closure(intensity));
        }
    }
}

Closure x Cacherの利用

struct Cacher<T> としてClosureの結果をキャッシュすることで、無駄な処理がなく、かつ、最大約2秒の処理とすることが可能です。

use std::thread;
use std::time::Duration;

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value, simulated_random_number);
}

///
/// トレーニング計画を出力する関数
///
fn generate_workout(intensity: u32, random_number: u32) {
    // 実行に約2秒かかるキャッシュを利用した仮想クロージャー
    let mut expensive_result = Cacher::new(|num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    });

    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_result.value(intensity));
        println!("Next, do {} situps!", expensive_result.value(intensity));
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                expensive_result.value(intensity)
            );
        }
    }
}

///
/// キャッシュロジック
///
struct Cacher<T>
where
    T: Fn(u32) -> u32,
{
    calculation: T,
    value: Option<u32>,
}

///
/// キャッシュロジック impl
///
impl<T> Cacher<T>
where
    T: Fn(u32) -> u32,
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation,
            value: None,
        }
    }

    fn value(&mut self, arg: u32) -> u32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v);
                v
            }
        }
    }
}

まとめ

github.com

  • let a = |b| { ... } によってClosureを宣言可能

最後に

Closuresにはまだまだ細かく見るべき点が複数ありますが、一旦今回はここまでにします。

明日、Closures続きを記載します。