なぜ関数のタイプクラスがないのですか?


17

私がいじくり回してきた学習の問題で、適用、作成などの操作を行う関数の型クラスが必要であることに気付きました。理由...

  1. 関数の表現を関数そのものであるかのように扱うと便利な場合があります。そのため、関数の適用は暗黙的にインタープリターを使用し、関数の構成は新しい記述を導き出します。

  2. 関数の型クラスを取得したら、特別な種類の関数の型クラスを派生させることができます-私の場合、可逆関数が必要です。

たとえば、整数オフセットを適用する関数は、整数を含む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つのアイテムのみを別のアイテムに対してしか定量化できない場合があります。私はその関数の表現を導出できるようにしたい、それは一般に、いくつかの提供された関数(ユニオン/検索ツリーの共通の祖先まで歩く)といくつかの逆関数(もう一方に戻る)の構成になります項目)。

単純な場合-元の「関数」が整数オフセット「関数」に制限されている場合、合成結果を整数オフセット「関数」として欲しい-コンポーネントのオフセットを追加します。これは、構成関数がアプリケーション関数と同様にクラス内にある必要がある理由の大きな部分です。

これは、操作を提供できないpurereturnまたはarr自分のタイプに対応できないため、、またはを使用できないことApplicativeMonad意味しArrowます。

これは、これらのタイプの失敗ではありません-抽象化の不一致です。私が望む抽象化は、単純な純粋な関数です。たとえば、副作用はありません。また、すべての機能に適用される標準(。)に相当するもの以外の機能をシーケンスおよび構成するための便利な表記法を作成する必要はありません。

私はできたインスタンスCategory。私はすべての機能的なものがアイデンティティを提供できると確信していますが、おそらく必要ではありません。しかし、Categoryアプリケーションをサポートしていないので、とにかくその操作を追加するには派生クラスが必要です。


2
私を夢中にさせますが、あなたがそれを説明しているようなタイプクラスを考えると、それは適用や作曲などのためのものです。おそらくあなたが考えている型クラスでしょうか?
ジミー・ホッファ

1
Applicativeはまったく正しいとは思いません-関数だけでなく値もラップする必要がありますが、関数をラップしたいだけで、ラップされた関数は実際には関数ですが、ラップされた関数は通常(最も一般的なケースは、関数を記述するASTです)。where <*>has type f (a -> b) -> f a -> f b、タイプg a b -> a -> bwhereのアプリケーションオペレーターとラップされた関数のドメインとコドメインab指定しますが、ラッパーの内部は(必ずしも)実際の関数ではありません。矢印で-おそらく、私は見てみましょう。
Steve314

3
逆にしたい場合、それはグループを意味しませんか?
jk。

1
@jk。OPが彼が探しているものを見つけるのにつながる可能性のある関数の逆関数について読むべきことがたくさんあることを理解するのは素晴らしい点です。以下は、このトピックに関する興味深い読み物です。しかし、Google for Haskell関数の逆は、多くの好奇心をそそる読書コンテンツを提供します。おそらく、彼はただData.Group望んでいる
ジミー・ホッファ

2
@ Steve314合成機能はモノイダルなカテゴリーだと思いました。ドメインとコドメインが常に同じ場合、それらはモノイドです。
ティムセギーン

回答:


14

まあ、私は自分自身を「機能的」なものを表すものとして売り込むアイデアを焼き付けていることを知りません。しかし、近くに来るいくつかがあります

カテゴリー

アイデンティティーと構成を持つ単純な機能コンセプトがある場合、カテゴリーがあります。

class Category c where
  id :: c a a
  (.) :: c b c -> c a b -> c a c

欠点は、あなたがオブジェクトのセット(と素敵なカテゴリのインスタンスを作成することができないということであるabc)。おそらくカスタムカテゴリクラスを作成できます。

矢印

関数に製品の概念があり、任意の関数を挿入できる場合、矢印はあなたのためです

 class Arrow a where
   arr :: (b -> c) -> a b c
   first :: a b c -> a (b, d) (c, d)
   second :: a b c -> a (d, b) (d, c)

ArrowApply あなたが望むものにとって重要に見えるアプリケーションの概念を持っています。

出願人

アプリケーションにはアプリケーションの概念があります。私はASTで関数アプリケーションを表すためにそれらを使用しました。

class Functor f => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f b -> f c

他にも多くのアイデアがあります。しかし、共通のテーマは、関数を表すデータ構造を構築し、それを解釈関数に渡すことです。

これは、無料のモナドがいくつ動作するかを示します。あなたが勇気を持っているなら、これらを突くことをお勧めします、彼らはあなたが提案しているもののための強力なツールであり、基本的にdo表記法を使用してデータ構造を構築し、それを異なる関数で計算に影響する副作用に折りたたみます。しかし、これらの関数はデータ構造上で動作するだけであり、どのようにすべてを作成したかを実際には認識していないという利点があります。これがあなたの通訳の例として提案したいことです。


カテゴリにアプリケーションがないようです- ($)。矢印は一見すると大げさすぎるように見えますが、まだArrowApply有望に聞こえます-何も提供する必要がない限り、それは大丈夫かもしれません。しばらくの間+1を行い、さらに確認します。
Steve314

3
@ Steve314カテゴリーDOアプリケーションが不足しているが、モナドは、それらを実行するための普遍的な方法が不足している、彼らは便利じゃないという意味ではありません
ダニエルGratzer

そこ私が使用することはできません一般的な理由はありますApplicativeArrow(またはMonad) -私のタイプの値はので、私は(一般に)正常な機能をラップすることはできません表す機能をされますが、表現データによって、およびもしあれば、任意の機能をサポートしていません。翻訳する方法がありました。つまりpure、を提供できない、arrまたはreturnインスタンスを提供できません。ところで-これらのクラスは便利ですが、この特定の目的に使用することはできません。Arrow「大量のやり過ぎ」ではありません-それは、私が最後に論文を読もうとしたとき、それを理解する準備ができていなかったときからの誤った印象でした。
Steve314

@ Steve314データを構築するためにモナドインターフェイスを提供するというアイデアは、無料のモナドを使用する目的です。それらをチェックしてください
ダニエルグラッツァー

Haskell Exchange 2013からビデオを見ました-AndresLöhは間違いなくそれをよく説明していますが、おそらくもう一度見たり、テクニックを試したりする必要があります。しかし、ここでそれが必要かどうかはわかりません。私の目標は、関数ではない(ただしインタープリター関数を持つ)表現を使用して、関数を抽象化することです。副作用の抽象化は必要ありません。また、シーケンス処理のための明確な表記法も必要ありません。この関数の抽象化が使用されると、アプリケーションと構成は、別のライブラリのアルゴリズム内で一度に1つずつ行われます。
Steve314

2

あなたが指摘するように、ここでApplicativeを使用することの主な問題は、の賢明な定義がないことですpure。したがって、Apply発明されました。少なくとも、それは私の理解です。

残念ながら、私はそのようなインスタンスの手元にも例ApplyはありませんApplicative。これはに当てはまると言われていますがIntMap、その理由はわかりません。同様に、あなたの例-オフセット整数-がApplyインスタンスを許可するかどうかはわかりません。


これはコメントのようなものです。回答方法
-gnat

ごめんなさい。これは多かれ少なかれ私の最初の答えです。
user185657

答えを改善するにはどうすればよいですか?
user185657

「なぜ関数の型クラスが存在しないのか、「存在しないから」または「思ったほど有用ではないから」という質問に対する回答がどのように解決されるかを読者が理解できるように編集を検討してください。アイデアに根本的な問題がありますか?」
gnat

1
これが良いことを願っています
user185657

1

前述に加えてCategoryArrowApplicative

またData.Lambda、Conal Elliottによって発見されました。

ラムダのような構造を持ついくつかの関数のようなクラス

もちろんおもしろそうですが、例がなければ理解するのが難しい...

ライブラリーの作成を引き起こしたものの1つであると思われる有形価値(TV)に関する例は、WikiページにありTypeComposeます。入力と関数値の出力を参照してください。

TVライブラリのアイデアは、Haskellの値(関数を含む)を具体的な方法で表示することです。

むき出しのロンクを投稿しないというStackOverflowのルールに従うために、これらのことを理解できるように、以下のビットをコピーします。

最初の例は次のとおりです。

apples, bananas :: CInput Int
apples  = iTitle "apples"  defaultIn
bananas = iTitle "bananas" defaultIn

shoppingO :: COutput (Int -> Int -> Int)
shoppingO = oTitle "shopping list" $
            oLambda apples (oLambda bananas total)

shopping :: CTV (Int -> Int -> Int)
shopping = tv shoppingO (+)

実行時に付与されますrunIO shopping(その他のコメント、GUI、およびその他の例については、そこを参照してください):

shopping list: apples: 8
bananas: 5
total: 13

これは質問にどのように対処しますか?見る回答する方法
ブヨ

@gnatの定義はData.Lambda関数のようなもの(要求された)のクラスを与えると思った...これらの物がどのように使われるのかわからなかった。これについて少し調べました。おそらく、それらは関数アプリケーションの抽象化を提供しません。
imz-イワンザカリヤシェフ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.