私がいじくり回してきた学習の問題で、適用、作成などの操作を行う関数の型クラスが必要であることに気付きました。理由...
関数の表現を関数そのものであるかのように扱うと便利な場合があります。そのため、関数の適用は暗黙的にインタープリターを使用し、関数の構成は新しい記述を導き出します。
関数の型クラスを取得したら、特別な種類の関数の型クラスを派生させることができます-私の場合、可逆関数が必要です。
たとえば、整数オフセットを適用する関数は、整数を含むADTで表すことができます。これらの関数を適用することは、整数を追加することを意味します。合成は、ラップされた整数を追加することにより実装されます。逆関数では、整数が否定されます。恒等関数はゼロをラップします。定数関数は、適切な表現がないため提供できません。
もちろん、値が本物のHaskell関数であるかのようにスペルする必要はありませんが、アイデアが得られたら、そのようなライブラリは既に存在し、おそらく標準のスペルを使用しているはずです。しかし、Haskellライブラリにはそのようなタイプクラスが見つかりません。
Data.Functionモジュールを見つけましたが、タイプクラスはありません-Preludeからも利用できる一般的な関数がいくつかあります。
だから-なぜ関数の型クラスがないのですか?「ないから」とか「思ったほど役に立たないから」?それとも、アイデアに根本的な問題があるのでしょうか?
私がこれまで考えていた最大の問題は、ループの問題を回避するために、実際の関数への関数の適用はおそらくコンパイラーによって特別に行われる必要があるということです。この関数を適用するには、関数適用関数を適用する必要があり、それを行うには、関数アプリケーション関数を呼び出す必要があります...
より多くの手がかり
私が目指しているものを示すためのサンプルコード...
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
-- In my first version, Doable only had the one argument f. This version
-- seemed to be needed to support the UndoableOffset type.
--
-- It seems to work, but it also seems strange. In particular,
-- the composition function - a and b are in the class, but c isn't,
-- yet there's nothing special about c compared with a and b.
class Doable f a b where
fwdApply :: f a b -> a -> b
compDoable :: f b c -> f a b -> f a c
-- In the first version, I only needed a constraint for
-- Doable f a b, but either version makes sense.
class (Doable f a b, Doable f b a) => Undoable f a b where
bwd :: f a b -> f b a
bwdApply :: f a b -> b -> a
bwdApply f b = fwdApply (bwd f) b
-- Original ADT - just making sure I could wrap a pair of functions
-- and there were no really daft mistakes.
data UndoableFn a b = UFN { getFwd :: a -> b, getBwd :: b -> a }
instance Doable UndoableFn a b where
fwdApply = getFwd
compDoable f g = UFN ((getFwd f) . (getFwd g)) ((getBwd g) . (getBwd f))
instance Undoable UndoableFn a b where
bwd f = UFN (getBwd f) (getFwd f)
bwdApply = getBwd
-- Making this one work led to all the extensions. This representation
-- can only represent certain functions. I seem to need the typeclass
-- arguments, but also to need to restrict which cases can happen, hence
-- the GADT. A GADT with only one constructor still seems odd. Perhaps
-- surprisingly, this type isn't just a toy (except that the whole thing's
-- a toy really) - it's one real case I need for the exercise. Still a
-- simple special case though.
data UndoableOffset a b where
UOFF :: Int -> UndoableOffset Int Int
instance Doable UndoableOffset Int Int where
fwdApply (UOFF x) y = y+x
compDoable (UOFF x) (UOFF y) = UOFF (x+y)
instance Undoable UndoableOffset Int Int where
bwdApply (UOFF x) y = y-x
bwd (UOFF x) = UOFF (-x)
-- Some value-constructing functions
-- (-x) isn't shorthand for subtraction - whoops.
undoableAdd :: Int -> UndoableFn Int Int
undoableAdd x = UFN (+x) (\y -> y-x)
undoableMul :: Int -> UndoableFn Int Int
undoableMul x = UFN (*x) (`div` x)
-- With UndoableFn, it's possible to define an invertible function
-- that isn't invertible - to break the laws. To prevent that, need
-- the UFN constructor to be private (and all public ops to preserve
-- the laws). undoableMul is already not always invertible.
validate :: Undoable f a b => Eq a => f a b -> a -> Bool
validate f x = (bwdApply f (fwdApply f x)) == x
-- Validating a multiply-by-zero invertible function shows the flaw
-- in the validate-function plan. Must try harder.
main = do putStrLn . show $ validate (undoableAdd 3) 5
putStrLn . show $ validate (undoableMul 3) 5
--putStrLn . show $ validate (undoableMul 0) 5
fb1 <- return $ UOFF 5
fb2 <- return $ UOFF 7
fb3 <- return $ compDoable fb1 fb2
putStrLn $ "fwdApply fb1 3 = " ++ (show $ fwdApply fb1 3)
putStrLn $ "bwdApply fb1 8 = " ++ (show $ bwdApply fb1 8)
putStrLn $ "fwdApply fb3 2 = " ++ (show $ fwdApply fb3 2)
putStrLn $ "bwdApply fb3 14 = " ++ (show $ bwdApply fb3 14)
アプリケーションには、統一された値が等しくないが、それらの可逆関数を介して関連する一種の統一が含まれます-プロローグスタイルのロジックですが、a = f(b)
制約ではなくa = b
。コンポジションのほとんどは、ユニオン検索構造を最適化することにより生じます。逆関数の必要性は明らかです。
統一セット内のアイテムに正確な値がない場合、特定のアイテムは、その統一セット内の別のアイテムに対してのみ数量化できます。そのため、「実際の」関数を使用したくない-それらの相対値を計算します。関数全体を削除して、絶対量と相対量だけにすることができます-おそらく数字/ベクトルだけが必要です(+)
-しかし、私の内部の建築宇宙飛行士は彼の楽しみを望んでいます。
リンクを再度分割する唯一の方法は、バックトラッキングを使用することであり、すべてが純粋です-union-findはキーを使用しIntMap
て「ポインタ」として実行されます。単純なユニオン検索機能がありますが、まだ反転可能な関数を追加していないので、ここにリストする意味はありません。
Applicative、Monad、Arrowなどを使用できない理由
私が提供する関数抽象化クラスが必要な主な操作は、アプリケーションと構成です。そのおなじみの音-例えばApplicative
(<*>)
、Monad
(>>=)
とは、Arrow
(>>>)
すべての構成機能です。ただし、私の場合、関数の抽象化を実装する型には、関数を表すデータ構造が含まれていますが、関数ではなく(関数を含むことはできません)、関数の限られたセットのみを表すことができます。
コードの説明で述べたように、「統一された」クラスター内のアイテムに正確な値がないため、1つのアイテムのみを別のアイテムに対してしか定量化できない場合があります。私はその関数の表現を導出できるようにしたい、それは一般に、いくつかの提供された関数(ユニオン/検索ツリーの共通の祖先まで歩く)といくつかの逆関数(もう一方に戻る)の構成になります項目)。
単純な場合-元の「関数」が整数オフセット「関数」に制限されている場合、合成結果を整数オフセット「関数」として欲しい-コンポーネントのオフセットを追加します。これは、構成関数がアプリケーション関数と同様にクラス内にある必要がある理由の大きな部分です。
これは、操作を提供できないpure
、return
またはarr
自分のタイプに対応できないため、、またはを使用できないことApplicative
をMonad
意味しArrow
ます。
これは、これらのタイプの失敗ではありません-抽象化の不一致です。私が望む抽象化は、単純な純粋な関数です。たとえば、副作用はありません。また、すべての機能に適用される標準(。)に相当するもの以外の機能をシーケンスおよび構成するための便利な表記法を作成する必要はありません。
私はできたインスタンスCategory
。私はすべての機能的なものがアイデンティティを提供できると確信していますが、おそらく必要ではありません。しかし、Category
アプリケーションをサポートしていないので、とにかくその操作を追加するには派生クラスが必要です。
Applicative
はまったく正しいとは思いません-関数だけでなく値もラップする必要がありますが、関数をラップしたいだけで、ラップされた関数は実際には関数ですが、ラップされた関数は通常(最も一般的なケースは、関数を記述するASTです)。where <*>
has type f (a -> b) -> f a -> f b
、タイプg a b -> a -> b
whereのアプリケーションオペレーターとラップされた関数のドメインとコドメインa
をb
指定しますが、ラッパーの内部は(必ずしも)実際の関数ではありません。矢印で-おそらく、私は見てみましょう。