正規分布、ステートマシン、ハッシュテーブル、任意の述語、文字列、凸包、アフィン変換、構成ファイル、CSSスタイルに共通するものは何ですか? そして、整数、Haskellの型、任意のグラフ、代替ファンクター、行列、正規表現、統計サンプルを統合するものは何ですか? 最後に、ブール代数、電気回路、長方形テーブル、パイプや建物の断熱材、平面上の画像を何らかの方法で相互接続することは可能ですか? これらの質問に対する2つの重要な答えがあります。1)これらすべてのオブジェクトを扱うプログラマー、2)これらのオブジェクトは類似の代数構造を持っています。1つ目はモノイド、2つ目は半環、3つ目はドモルガン代数です。
プログラマーが自分自身で学習し、コンピューターにモノイド、ハーフリング、その他の構造の操作方法を教えると、私たち全員が簡単に作業できるようになります。 代数構造には一定の制限が課せられており、制限から一定の保証が続くためです。 これらの制限と保証はすべて 、構造のすべての代表に対して有効です。つまり、モノイド、半環、代数、したがって上記および他の多くのオブジェクトすべてを操作するためのユニバーサルツールボックスを構築することができます。 代数構造を定義した数学者の数を確認してください ! そして、それらには定理があり、プログラマにとって有用なもの、まだあまり良くないものもありますが、それらはすべて最高のプログラムライブラリのように信頼性があります!
準同型が構造内に構築されると、すべてがさらにきれいになります-構造を保持する変換。 準同型は、構造のあるオブジェクトから別のオブジェクトに「切り替える」ことを可能にします-リストから数字、グラフから行列、正規表現からグラフ、電気回路からブール式、またはこれらの回路のグラフィック画像へ! まあ、同型は一般的に歌です! プログラマーが数学を必要とせず、これらのすべての射-モルフィズムが単なるナンセンスであると信じている場合、彼は美しく信頼できるツールだけでなく、最も重要なことには、仕事の喜びの本質的な部分を奪います。
この記事では、de Morgan代数の例によって、代数的抽象化の構築方法、準同型性とそれらの代数を定義する方法、そしてもちろんこれを使用する方法を示します。
この記事は、型クラス、ファンクター、モノイドが何であるかを既に知っており、一般的にHaskellプログラミングとは何かをよく知っている人を対象としています。 一方、プログラミングで抽象化がどのように構築され、実際の問題にそれらをどのように適用できるかに興味がある人は誰でも興味を持ちます。
モノイドについて少し
モノイドは構成の抽象化です。 同様に、合成は関数型プログラミングの重要な概念です。 モノイドがここで頻繁に見られ、そのような重要な役割を果たすのはそのためです。 モノイドの使用の価値と豊かさについて多くのことが言われ、書かれています。 ここで、それらの主要なプロパティをリストします。これは、以降のプレゼンテーションで役立ちます。
定義
非常に単純な構造はモノイドと呼ばれます。これは、単一のニュートラル要素を持つ連想バイナリ操作が定義されるセットです。 操作の可換性は必須ではありませんが、ニュートラル要素はニュートラルでなければならず、第1オペランドと第2オペランドの両方である必要があります。
HaskellはMonoid
クラスを定義します。
class Monoid a where mempty :: a mappend :: a -> a -> a
Data.Monoid
ライブラリは、 Sum
、 Product
、 First
、 Last
、 Endo
など、このクラスの便利なインスタンスを多数提供します。
モノイドの重要な特性は、中立要素の結合性と一意性です。 これらの制限により、任意の実行順序(並列を含む)でモノイド操作を一般化できます。
デュアルモノイド
任意のモノイドに対して、バイナリ演算が逆の順序で引数を取るデュアルモノイドを定義できます。
> Dual [1,2,3] <> Dual [5,6] Dual { getDual = [5,6,1,2,3] }
畳み込みリンク
ツールが畳み込みでどれほど便利で普遍的であるかはよく知られています:一方では、コレクションを処理する多数の便利な汎用関数が畳み込みによって表現され、他方では、リスト、ツリー、および他の帰納的コレクションを折りたたむことができます。 多くのバンドルが定義されています。 コレクションを右側と左側の両方で折りたたむことができます。これは、結果(折り畳み関数の結合性に応じて)と計算の効率に影響する可能性があります。 空のコレクションの場合は、畳み込みの初期結果を入力するか、コレクションが空でないことが保証されている場合は、畳み込みなしで実行することにより、折りたたむことができます。 したがって、Haskell標準ライブラリとData.Foldable
ライブラリにData.Foldable
、非常に多くの異なる畳み込みがあります。
foldr, foldl, foldr1, foldl1, foldl', foldl1'
モノイドへの畳み込みについて話している場合、モノイド動作の連想性と中立要素の存在の保証により、1つの関数で十分です。
fold :: (Monoid m, Foldable t) => tm -> m
最も一般的に使用されるオプションは、畳み込みに使用する特定のモノイドを明示的に示すことです。
foldMap :: (Monoid m, Foldable t) => (a -> m) -> ta -> m
コレクションがファンクターの場合、 foldMap
関数のfoldMap
は次のように指定できます。
foldMap f = fold . fmap f
無料モノイド
時々、モノイド間で「通過」する可能性があります。そのような変換は準同型と呼ばれることを思い出してください。 さらに、準同型を他のものに構築できるモノイドが存在します。 このようなモノイドはフリーと呼ばれ、リストはHaskellで役割を果たします。
自由構造は代数システムの特性を反映しますが、何も「計算」したり、データを変更したり、情報を失ったりしません。 ある意味では、それらは代数システムまたはカテゴリの要素を記述するための正式な言語であり、準同型はこの言語のインタープリターと見なすことができます。 このようなアナロジーは、将来的に私たちに役立つでしょう。
Haskellでのモノイドとその応用についての記事を読んでください:
代数デモーガン
多くのオブジェクトには、いくつかの合成方法があり、1つのモノイドはここではできません。 そのような方法が2つある場合、リング、ハーフリング、ラティス、およびさまざまな代数について説明します。 それらの基礎となる代数構造の共通性を示唆するいくつかの例を考えてみましょう。
ブール代数
ブール代数について私たちは何を知っていますか:
- 値のセット
{True, False}
で定義され、3つの操作(接続詞、選言、否定)を使用して記述されます。 - 論理積と分離はモノイドを形成します。
-
False
要素は、選言に対しては中立的であり、連結に対しては吸収的です。 -
True
要素は、順番に、結合のための中立的な要素であり、選言のための吸収的な要素です。 - 拒否はインボリューションです 。つまり、この操作はそれ自体の逆です。
- de Morganの法律が実施されています。
!( A \ウカフェッジベッド) = ! V E E !ベッドと、 \クワッド!( V E Eのベッド) = ! \くさんび!ベッドと
電気抵抗の代数
抵抗から成り、回路要素の直列または並列接続を可能にする任意のバイポーラ電気回路を計算するタスクを見てみましょう。
- それらに対して2つの順次操作が定義されています( L個のE 、F tはrはI G H T A R R O W )および並列( \ご覧ください上下さい矢印 )接続。
- 化合物はモノイドを形成します(結合性があり、中性の要素を持っています)。
- 開回路は並列では中立であり、直列接続では吸収されます。
- 短絡は直列では中立であり、並列接続では吸収されます。
- 抵抗は逆数に対応します-伝導性と抵抗から伝導性への移行は退屈です。
- シリアルおよびパラレル接続の実効抵抗は、次の式で計算されます。
R_1 \ leftrightarrow R_2 = R_1 + R_2、\ quad R_1 \ updownarrow R_2 = \ frac {1} {\ frac {1} {R_1} + \ frac {1} {R_2}
R_1 \ leftrightarrow R_2 = R_1 + R_2、\ quad R_1 \ updownarrow R_2 = \ frac {1} {\ frac {1} {R_1} + \ frac {1} {R_2}
これらの式は対称的な方法で書き直すことができます。
frac1R1 updownarrowR2= frac1R1 leftrightarrow frac1R2、 quad frac1R1 leftrightarrowR2= frac1R1 updownarrow frac1R2、
デモーガンの法律が認められています!
テーブル代数
長方形のテーブルは、主に2つの方法で結合できます。行を結合する( leftrightarrow )または列( \上下矢印 ):
1 2 3 ab 1 2 3 ab 4 5 6 <-> cd = 4 5 6 cd 7 8 9 ef 7 8 9 ef 1 2 3 abc 1 2 3 4 5 6 <|> def = 4 5 6 7 8 9 7 8 9 abc def
さらに、インボリューション-転置はテーブルに対して定義でき、テーブルを結合する両方の方法が同じ中立要素(空のテーブル)でモノイドを形成することを確認するのは簡単です。 最後に、別の方法で結合方法を取得できます。たとえば、最初に両方のテーブルを転置し、次に水平に結合して結果を転置することにより、2つのテーブルを垂直に結合できます。
A leftrightarrowB=(A top updownarrowB top) top、 quadA updownarrowB=(A top leftrightarrowB top) top
インボリューションの特性を使用して、再びデモーガンの法則を取得します。
(A leftrightarrowB) top=A top updownarrowB top、 quad(A updownarrowB) top=A top leftrightarrowB top
ド・モルガン代数の定義
作業する構造の正式な定義を与える時が来ました。
De Morgan代数は、2つの二項演算が定義されているセットで構成される構造です + そして \回 条件付きで加算と乗算、およびインボリューション演算と呼ばれる ′ 以下の条件に該当する場合:
- 操作 + 中立要素とモノイドを形成する $インライン$ 0 $インライン$ :
a+0=0+a=a、 quada+(b+c)=(a+b)+c;
操作 \回 中立要素とモノイドを形成する 1 :
a times1=1 timesa=a、a times(b timesc)=(a timesb) timesc;
ゼロの乗法特性が満たされます。0 timesx=x times0=0;
インボリューションプロパティ:(x′)′=x;
インボリューションに対するゼロと1の双対性:0′=1、1′=0;
モーガンの法則:(A+B)′=A′ timesB′、(A timesB)′=A′+B′。
厳密に言えば、乗算は加算に関して分配的である必要がありますが、場合によっては(たとえば、電気回路の場合)、これは正しくない場合があります。 しかし、それが満たされている場合、加算と乗算の組み合わせで構成される複雑な式は、すべての括弧を開くことで単項式の合計の形に縮小できます。逆に、括弧から共通の因子を取り出すことで式を単純化することもできます。
場合によっては、インボリューションは反転の役割を果たします。つまり、乗算演算の逆要素を生成します。 その後、次のアイデンティティが保持されます。
x timesx′=x′ timesx=
これはブール代数と抵抗代数には当てはまりますが、テーブルには当てはまりません。
Haskell上のde Morgan代数の抽象化
作業を開始する前に、いくつかの拡張機能を接続します。
{-# LANGUAGE DeriveFunctor, FlexibleInstances, GeneralizedNewtypeDeriving #-}
de Morgan代数の型のクラスを定義します。
class DeMorgan a where {-# MINIMAL inv,((<->)|(<|>)),(zero|one) #-} inv :: a -> a zero :: a zero = inv one one :: a one = inv zero (<->) :: a -> a -> a a <-> b = inv (inv a <|> inv b) (<|>) :: a -> a -> a a <|> b = inv (inv a <-> inv b)
この定義では、クラスでの作業を簡素化するために、すでにde Morganの法則を使用しています。 標本の説明では、2つのモノイド操作、その中立要素とインボリューションのうちの1つだけを決定すれば十分です。
論理データの
DeMorgan
クラスの最初のインスタンスを作成します。
instance DeMorgan Bool where zero = False inv = not (<->) = (&&)
もちろん、効率上の理由から、
<|>
演算子も明示的に定義する価値がありますが、この簡潔な定義のままにして、Bool
型の代数を完全に定義するようにします。
> True <|> False True > one :: Bool True
ブール代数、それは良いですが、あまりにも簡単です。 Lotfie Zadehの精神でファジーロジックの代数を構築することでそれを要約しましょう。 これを行うには、
Fuzzy
ラッパータイプを定義し、コンパイラーに数値プロパティを導出するよう要求します。
newtype Fuzzy = Fuzzy { deFuzzify :: Double } deriving (Show, Num, Fractional, Ord, Eq) instance DeMorgan Fuzzy where zero = 0 inv x = fuzzify $ 1 - x a <|> b = fuzzify $ max ab fuzzify x = 0 `max` x `min` 1
> deFuzzify $ one 1 > deFuzzify $ 0.2 <-> 0.4 0.2 > deFuzzify $ 0.8 <|> 0.4 <-> 0.5 0.5 > deFuzzify $ 0.3 <|> 0.4 <-> 0.5 0.4
ネストされたリストで表されるテーブルの最も単純なde Morgan代数を定義します。
instance DeMorgan [[a]] where zero = [] (<|>) = (++) inv = transpose
ここでは、
Data.List
ライブラリのtranspose
関数を使用しました。 この代数の仕組みは次のとおりです。
> inv [[1,2],[3,4]] [[1,3],[2,4]] > [[1,2],[3,4]] <-> [[5],[6]] [[1,2,5],[3,4,6]] > [[1,2],[3,4]] <|> [[5,6]] [[1,2],[3,4],[5,6]]
単純な表形式の関数を作成するために使用しましょう
table :: (Show a, Show b) => (a -> a -> b) -> [a] -> [[String]] table f vals = ([[" "]] <-> h) <|> (inv h <-> c) where h = [show <$> vals] c = [show <$> [ fxy | x <- vals] | y <- vals] showTable tbl = putStrLn $ unlines $ unwords <$> tbl
> showTable $ table mod [1..6] 1 2 3 4 5 6 1 0 0 0 0 0 0 2 1 0 1 0 1 0 3 1 2 0 1 2 0 4 1 2 3 0 1 2 5 1 2 3 4 0 1 6 1 2 3 4 5 0
最後に、抵抗を思い出して、数字、より正確には、除算演算が定義されている数字タイプのド・モルガン代数を構築しましょう。 これを行うには、
Frac
ラッパータイプを定義します。
newtype Frac a = Frac {getFrac :: a} deriving (Show, Num, Fractional, Functor) instance Fractional a => DeMorgan (Frac a) where zero = 0 inv = (1/) (<->) = (+)
> getFrac $ 2 <-> 6 8.0 > getFrac $ 2 <|> 6 1.5 > getFrac $ 1 <-> (2 <|> (1 <-> 1)) 2.0
例として、図に示す回路の実効抵抗を表現し、12 Vの電圧での電流強度を計算します。
> getFrac $ ((6 <|> 12) <-> 4) <|> (3 <-> 5) 4.0 > getFrac $ 12 / ((6 <|> 12) <-> 4) <|> (3 <-> 5) 3.0
デュアル代数
複雑なばねシステムの有効剛性またはコンデンサバンクの容量を計算するには、
Frac
タイプも適していますが、これらの計算には、演算子<|>
と<->
、および要素zero
とone
を交換する必要があります。 数学者は、この変換で双対代数への移行を喜んで認識します。 良さそうに聞こえますが、スプリングの並列接続を指す2 <-> 3
書きたくありません。 演算子のセマンティクスを維持するのは良いことですが、計算の実行方法を変更します。
ここでも、数学はその成果を活用する機会を与えてくれます。 モノイドについては、ドモルガン代数については、「すべてが逆」の双対代数が定義されています。 Haskell言語でこの状況を説明します。 双対代数用の新しい
Dual
ラッパー型を作成し、a
がde Morgan代数である場合、双対もde Morgan代数になることをお知らせください。
newtype Dual a = Dual {getDual :: a} deriving (Show, Num, Fractional, Eq, Ord, Functor) instance DeMorgan a => DeMorgan (Dual a) where zero = Dual one one = Dual zero inv x = inv <$> x Dual a <|> Dual b = Dual $ a <-> b Dual a <-> Dual b = Dual $ a <|> b
これで、3つのバネのシステムの有効剛性を計算できます。そのうちの1つは直列に接続され、他のペアは並列に接続されています。
> getFrac . getDual $ 600 <-> (200 <|> 300) 450.0
型推論により、計算の「出口」でのみ使用する代数を指定できます。 したがって、明示的に、同時に式の評価方法を変更するだけです。
無料の代数
しかし、抵抗だけでなくキー、コンデンサ、コイルなどを含む回路を想像して計算したい場合はどうでしょうか? そして、カウントするだけでなく、それらを表現したり、ファイルに保存したりしますか? このためには、無料の代数が必要です。
チェーンは、何も計算しないデータ構造で表されますが、チェーンの代数構造を反映し、de Morgan代数を形成します。
data Circuit a = Zero | One | Elem a | Inv (Circuit a) | Par (Circuit a) (Circuit a) | Seq (Circuit a) (Circuit a) deriving (Show, Functor)</hs></pre> instance DeMorgan (Circuit a) where zero = Zero one = One (<->) = Seq (<|>) = Par inv = Inv
チェーン要素のインボリューションまたはインバージョンは明確な意味を持ちませんが、正確さと一般性のために、タイプの説明にそれを含めました。
最初の自然な準同型は、
Circuit
型からde Morgan代数が定義されている任意の型への変換です。
reduce :: DeMorgan a => Circuit a -> a reduce circ = case circ of Zero -> zero One -> one Elem b -> b Inv a -> inv (reduce a) Par ab -> reduce a <|> reduce b Seq ab -> reduce a <-> reduce b
reduce
関数はfold
関数に似ており、モノイドのリストを任意のモノイドにfold
ます。foldMap
関数をfold
関数からファンクター用に作成できるのと同じ方法で、foldMap
関数を作成できます。
reduceMap :: DeMorgan b => (a -> b) -> Circuit a -> b reduceMap = reduce . fmap f
この関数を使用すると、チェーンを解釈するときに使用するde Morgan代数を明示的に指定できます。
モノイドと私たちの代数の畳み込み関数の定義でそのような美しい対称性を見つけることは、審美的な喜びを与えることを認めなければなりません!
さまざまな回路設計タスク
チェーンを構築するために、オブジェクト指向型
Lumped
を整理します。これにより、Value a
回路の要素のパラメーターの値に加えて、Short CircuitShort
とCircuitBreak
を明示的に表すことができます。
data Lumped a = Short | Value a | Break deriving (Show, Functor) instance DeMorgan s => DeMorgan (Lumped s) where zero = Break Value r1 <-> Value r2 = Value $ r1 <-> r2 Short <-> r = r r <-> Short = r _ <-> Break = Break Break <-> _ = Break inv Short = Break inv Break = Short inv (Value r) = Value (inv r)
エレメントのタイプを紹介します:抵抗、コンデンサ、インダクタンス、および回路エレメントの設計者:
data Element = R Double | C Double | L Double deriving Show res = Elem . R cap = Elem . C coil = Elem . L key True = Zero key False = One
最後に、実験用の単純なチェーンを作成します。
s :: Bool -> Circuit Element sk = res 10 <-> ((res 2 <-> coil 5e-3 <-> key k) <|> cap 10e-9)
これで、自由代数のさまざまな解釈を構築する準備が整いました。
connect
関数は、ブール代数を使用して回路が閉じているかどうかを決定します。
connected :: Circuit Element -> Bool connected = reduceMap f where f el = case el of R _ -> True C _ -> False L _ -> True
> connected (s True) True > connected (s False) False
resistance
関数は、分数の代数を使用して、直流の実効回路抵抗を決定します。
resistance :: Circuit Element -> Lumped Double resistance = fmap getFrac . reduceMap (fmap Frac . f) where f el = case el of R r -> Value r C _ -> Break L _ -> Short
ここでは、計算作業は、ファンクターを宣言した
Lumped
型の内部で行われます。
> resistance (s True) Value 12 > resistance (s False) Break
回路のインピーダンスを計算するには、
Data.Complex
モジュールを接続する必要があります:
impedance :: Double -> Circuit Element -> Lumped (Complex Double) impedance w = fmap getFrac . reduceMap (fmap Frac . f) where f el = Value $ case el of R r -> r :+ 0 C c -> 1 / (0 :+ w*c) L l -> 0 :+ w*l
> impedance 1e3 (s False) Value (10.0 :+ (-99999.99999999997)) > impedance 1e3 (s True) Value (12.00020001420084 :+ 5.0002100065000405) > impedance 1e6 (s False) Value (10.0 :+ (-100.0)) > impedance 1e6 (s True) Value (10.000832986116954 :+ (-102.04081598653629))
コンデンサバンクの計算について話している場合は、代数をデュアルに変更する必要があります。
capacity :: Circuit Element -> Lumped Double capacity = fmap (getFrac . getDual) . reduceMap (fmap (Dual . Frac) . f) where f el = case el of R _ -> Short C c -> Value c L _ -> Short
これらすべての解釈において、回路の特性の計算方法を示し、個々の要素の計算方法のみを決定し、畳み込みに適した代数を選択するという事実に注意してください。
また、チェーンを描く必要がある場合は、優れたダイアグラムライブラリを使用してベクターグラフィックスを構築できます。 画像の相対的な配置のための2つのコンビネータを提供します:
(|||)
-横に並べて(===)
-縦に並べて これは、画像についてはテーブル代数に似た代数を構築し、チェーンの個々の要素がどのように表現されるかを説明した後、自由代数から画像代数へ準同型を構築し、任意のスキームを描くことができることを意味します。 ちなみに、モノイドの細工や非自明な使用を確認したい場合は、このライブラリとそれに関連する記事に注意してください。
おわりに
抽象化の使用がドモルガン代数に与えるすべての可能性を考慮しませんでした。 窓と複雑な壁装材を備えた建物の断熱を計算する例を挙げることができます。 または、剛性要素と粘性要素のチェーンを持つ非ニュートン流体のモデリング。
モノイドとド・モルガン代数については、一連の単純な定理が成り立ちます。例えば、モノイド型の積もモノイドを形成することが知られています。 同等のステートメントは、de Morgan代数についても当てはまります;それは、構造上の単一のパッセージで一度にいくつかの特性を計算することを可能にします。 さらに、モノイドで代数のエピモーフィズムを構築し、代数的特性だけでなく、モノイドの特性も計算するのは簡単です。 最も単純な例として、抵抗の計算と同時に、回路によって放出される合計電力を計算できます。これは、単純な加算モノイドによって説明されます。
自由代数は、たとえばファジーロジックの問題に不可欠なもう1つの可能性を与えます。 まだ計算されていない式は、代数の分布性を使用して単純化できます。 この手順は、プログラムの実行中、つまり任意のデータを使用して、ただし集中的な計算の前に実行できます。 自由構造ができることは、Wolfram Mathematicaで示されています。この式では、すべての式は本質的に自由代数です。
馬や車と比較して、列車には大きな制限があります-鉄道でしか移動できません。 また、鉄道には大きな制限があります-列車のみがそれに沿って移動できます。 しかし、これらの制限は新たな機会を提供します。摩擦を大幅に減らし、速度を上げ、何百もの列車に対応する複雑で効率的な自動モーション制御システムを構築できます。 バイクやトラクターでバイクに乗ることはできません。 これは制限です。 しかし、それを保証すれば、高速と高帯域幅を期待できます。
関数型プログラミングは、プログラマーの日常業務にさまざまな制限と法律を追加します。 状態を変更することは不可能であり、構成は連想的であり、適用可能なファンクターであり、モナドは適用可能なファンクターおよびモナドの法則などを満たさなければなりません。 これは面倒な場合がありますが、これらの制限と法律に伴い、コードがそのように機能することを保証します。 そして最も重要なことは、原則として、ファンクタ、モナド、連想性および分配性、代数、およびプログラムの他のカテゴリについて話し、したがって、多くの世代の数学者の成果を使用して日常の仕事に適用することができるのは、まさにこれらの法律の厳密な遵守です。 したがって、これらの複雑なものはすべて、FPの松葉杖ではなく、コンピューターのハードウェアの開発によって利用可能になった新しい機会です。 それらを無視しないでください。
- 操作 + 中立要素とモノイドを形成する $インライン$ 0 $インライン$ :