解釈された算術


9

あまり知られていない事実は、十分な言語拡張(ghc)をオンにすると、Haskellが動的に型付けされたインタープリター型言語になるということです。たとえば、次のプログラムは加算を実装しています。

{-# Language MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, UndecidableInstances #-}

data Zero
data Succ a

class Add a b c | a b -> c
instance Add Zero a a
instance (Add a b c) => Add (Succ a) b (Succ c)

これは本当にHaskellのようには見えません。オブジェクトを操作する代わりに、タイプを操作します。各番号は独自のタイプです。関数の代わりに、型クラスがあります。機能的な依存関係により、型間の関数として使用できます。

では、コードをどのように呼び出すのでしょうか。別のクラスを使用します

class Test a | -> a
 where test :: a
instance (Add (Succ (Succ (Succ (Succ Zero)))) (Succ (Succ (Succ Zero))) a)
  => Test a

これによりtest、タイプが4 + 3に設定されます。これをghciで開くとtest、タイプ7であることがわかります。

Ok, one module loaded.
*Main> :t test
test :: Succ (Succ (Succ (Succ (Succ (Succ (Succ Zero))))))

仕事

2つのペアノ数値(負でない整数)を乗算するクラスを実装してほしい。ペアノ数値は、上記の例と同じデータ型を使用して構築されます。

data Zero
data Succ a

そして、あなたのクラスも上記と同じ方法で評価されます。クラスには任意の名前を付けることができます。

バイトに費用をかけずに、必要なghc言語拡張機能を使用できます。

テストケース

これらのテストケースでは、クラスの名前がMであることを想定していますが、必要に応じて別の名前を付けることができます。

class Test1 a| ->a where test1::a
instance (M (Succ (Succ (Succ (Succ Zero)))) (Succ (Succ (Succ Zero))) a)=>Test1 a

class Test2 a| ->a where test2::a
instance (M Zero (Succ (Succ Zero)) a)=>Test2 a

class Test3 a| ->a where test3::a
instance (M (Succ (Succ (Succ (Succ Zero)))) (Succ Zero) a)=>Test3 a

class Test4 a| ->a where test4::a
instance (M (Succ (Succ (Succ (Succ (Succ (Succ Zero)))))) (Succ (Succ (Succ Zero))) a)=>Test4 a

結果

*Main> :t test1
test1
  :: Succ
       (Succ
          (Succ
             (Succ
                (Succ (Succ (Succ (Succ (Succ (Succ (Succ (Succ Zero)))))))))))
*Main> :t test2
test2 :: Zero
*Main> :t test3
test3 :: Succ (Succ (Succ (Succ Zero)))
*Main> :t test4
test4
  :: Succ
       (Succ
          (Succ
             (Succ
                (Succ
                   (Succ
                      (Succ
                         (Succ
                            (Succ
                               (Succ
                                  (Succ
                                     (Succ (Succ (Succ (Succ (Succ (Succ (Succ Zero)))))))))))))))))

テクニカルインタビューの入力からインスピレーションを得ます


言語拡張機能は無料ですか?もしそうなら、どれですか?
Potato44 '10 / 06/18

@ Potato44そうそう すべての言語拡張は無料です。
アドホックガーフハンター

1
へh ...そうではないのに、この投稿はミームのようです。
マジックタコの壷182年

回答:


9

130 121バイト

ØrjanJohansenによる-9バイト

type family a+b where s a+b=a+s b;z+b=b
type family a*b where s a*b=a*b+b;z*b=z
class(a#b)c|a b->c
instance a*b~c=>(a#b)c

オンラインでお試しください!

これは、加算(+)と乗算のための閉じた型ファミリを定義します(*)。次に、等式制約とともに型ファミリー(#)を使用して(*)型ファミリの世界から型クラスプロローグの世界に変換する型クラスが定義されます。


3
あなたが方程式を交換した場合は、交換することができますZeroによってz
Ørjanヨハンセン

1
@ØrjanJohansen完了。私は誰かのために9バイトを節約し、9バイトが私のために節約されます。
Potato44

タイプファミリーの使い方がわかりませんが、このような関数を定義する必要がないので+便利でしょうか。
リン、

@Lynnはもっと長くなってしまう。TIO
Potato44 2018年

1
@WheatWizard私はコメントで投稿したコードが長く出てきたため、本質的にはあなたの回答の末尾再帰バージョンであることを認識しました。
Potato44

6

139バイト

class(a+b)c|a b->c;instance(Zero+a)a;instance(a+b)c=>(s a+b)(s c)
class(a*b)c|a b->c;instance(Zero*a)Zero;instance((a*b)c,(b+c)d)=>(s a*b)d

オンラインでお試しください!

タイプ演算子を定義し*ます。Prologプログラムと同等:

plus(0, A, A).
plus(s(A), B, s(C)) :- plus(A, B, C).
mult(0, _, 0).
mult(s(A), B, D) :- mult(A, B, C), plus(B, C, D).

Potato44とHat Wizardはそれぞれ9バイトを節約しました。ありがとう!


データ宣言を合計バイト数にカウントする必要はありません。機会があったら、質問でこれをより明確にします
Ad Hoc Garf Hunter

また、一般のf代わりに使用できると思いますSucc
アドホックガーフハンター2018

1
コロンを捨てることで9バイト節約できます。
Potato44

ハットウィザードも6つではなく9つを保存したと思います。Succは3回発生しました。
Potato44

1

ファミリーバージョン、115バイト

type family(a%b)c where(a%b)(s c)=s((a%b)c);(s a%b)z=(a%b)b;(z%b)z=z
class(a#b)c|a b->c
instance(a%b)Zero~c=>(a#b)c

オンラインでお試しください!

これは、potato44のようなクローズドタイプファミリーを使用しています。他の回答とは異なり、私は1つのタイプファミリーのみを使用します。

type family(a%b)c where
  -- If the accumulator is Succ c:
  -- the answer is Succ of the answer if the accumulator were c
  (a%b)(s c)=s((a%b)c)
  -- If the left hand argument is Succ a, and the right hand is b
  -- the result is the result if the left were a and the accumulator were b
  (s a%b)z=(a%b)b
  -- If the left hand argument is zero
  -- the result is zero
  (z%b)z=z

これは、3つのタイプの演算子を定義します。それは本質的に実装し(a*b)+cます。右手引数を合計に追加したいときはいつでも、代わりにアキュムレータに入れます。

これにより(+)、まったく定義する必要がなくなります。技術的には、このファミリを使用して追加を実装できます

class Add a b c | a b -> c
instance (Succ Zero % a) b ~ c => Add a b c

Class-Version、137バイト

class(a#b)c d|a b c->d
instance(a#b)c d=>(a#b)(f c)(f d)
instance(a#b)b d=>(f a#b)Zero d
instance(Zero#a)Zero Zero
type(a*b)c=(a#b)Zero c

オンラインでお試しください!

このクラスバージョンは、ファミリバージョンにいくらかの根拠を失いますが、ここでは最短のクラスバージョンよりもまだ短いです。それは私の家族のバージョンと同じアプローチを使用しています。


いいですね、あなたのタイプファミリーは数学的にa * b + cを実装しているようです。その「除算」の言及は「追加」であることを意味していますか?
Potato44

ところで、あなたはたまたまあなた自身の仕様に違反しています。「ペアノの2つの数値を乗算するクラスを実装する」現在持っているのはクラスではありませんが、たまたまそうなのConstraintです。したがって、仕様を更新するか、型の同義語ではなくクラスを使用するフォームに戻す必要があります。タイプの同義語を使用すると、答えを96バイトに
減らす

@ Potato44私はクラスが制約をもたらす種類のものに過ぎないとの印象を受けました。おそらくそれは質問の明確さの欠如が原因でした。その後、115の回答に戻ります。
アドホックガーフハンター
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.