Aritmetica interpretata


9

Un fatto poco noto è che se si attivano abbastanza estensioni di linguaggio (ghc) Haskell diventa un linguaggio interpretato digitato dinamicamente! Ad esempio il seguente programma implementa l'aggiunta.

{-# 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)

Non sembra più Haskell. Per uno invece di operare su oggetti, operiamo su tipi. Ogni numero è il proprio tipo. Invece delle funzioni abbiamo classi di tipi. Le dipendenze funzionali ci consentono di usarle come funzioni tra i tipi.

Quindi, come possiamo invocare il nostro codice? Usiamo un'altra classe

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

Questo imposta il tipo di testal tipo 4 + 3. Se lo apriamo in ghci scopriremo che testè effettivamente di tipo 7:

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

Compito

Voglio che tu implementi una classe che moltiplica due numeri Peano (numeri interi non negativi). I numeri di Peano saranno costruiti usando gli stessi tipi di dati nell'esempio sopra:

data Zero
data Succ a

E la tua classe verrà valutata allo stesso modo di cui sopra. Puoi nominare la tua classe come preferisci.

È possibile utilizzare qualsiasi estensione ghc desiderata per byte senza alcun costo.

Casi test

Questi casi di test presuppongono che la tua classe sia nominata M, puoi nominarla in altro modo, se lo desideri.

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

risultati

*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)))))))))))))))))

Trae ispirazione dalla Digitazione dell'intervista tecnica


Le estensioni di lingua sono gratuite? Se sì, quali?
Potato44

@ Potato44 Oh sì. Tutte le estensioni di lingua sono gratuite.
Ad Hoc Garf Hunter,

1
Heh ... Questo post sembra meme-y anche se non lo è.
Magic Octopus Urn,

Risposte:


9

130 121 byte

-9 byte grazie a Ørjan Johansen

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

Provalo online!

Questo definisce le famiglie di tipi chiuse per addizione (+)e moltiplicazione (*). Quindi (#)viene definita una classe di tipo che utilizza la (*)famiglia di tipi insieme a un vincolo di uguaglianza per convertire dal mondo delle famiglie di tipi al mondo del prologo della tabella dei tipi.


3
Se si scambiano le equazioni, è possibile sostituire Zerocon z.
Ørjan Johansen,

1
@ ØrjanJohansen Fatto. Risparmio 9 byte per qualcuno e 9 byte vengono salvati per me.
Potato44

Non so come usare le famiglie di tipi, ma forse una funzione come questa quindi non è necessario definire +è utile?
Lynn,

@Lynn che finisce per uscire più a lungo. TIO
Potato44

1
@WheatWizard Mi sono appena reso conto che il codice che ho pubblicato nel commento perché è uscito più a lungo è essenzialmente la versione ricorsiva della coda della tua risposta.
Potato44

6

139 byte

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

Provalo online!

Definisce un operatore di tipo *. Equivalente al programma 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 e Hat Wizard hanno salvato 9 byte ciascuno. Grazie!


Non è necessario contare le dichiarazioni dei dati per il totale dei byte. Lo chiarirò nella domanda quando ne avrò la possibilità
Ad Hoc Garf Hunter il

Inoltre penso che tu possa usare un generale finvece di Succ.
Ad Hoc Garf Hunter,

1
Puoi salvare 9 byte abbandonando i due punti.
Potato44

Penso che anche Hat Wizard abbia salvato 9, non 6. Vi sono state tre occorrenze di Succ.
Potato44

1

Versione familiare, 115 byte

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

Provalo online!

Questo utilizza una famiglia di tipi chiusi come potato44 . Tranne a differenza dell'altra risposta, utilizzo solo una famiglia di tipi.

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

Questo definisce un operatore su tre tipi. In sostanza implementa (a*b)+c. Ogni volta che vogliamo aggiungere il nostro argomento della mano destra al totale, invece lo inseriamo nell'accumulatore.

Questo ci impedisce di dover definire del (+)tutto. Tecnicamente puoi usare questa famiglia per implementare l'aggiunta facendo

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

Versione di classe, 137 byte

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

Provalo online!

Questa versione di classe perde terreno rispetto alla versione di famiglia, tuttavia è ancora più breve della versione di classe più corta qui. Utilizza lo stesso approccio della mia versione di famiglia.


Bene, vedo che la tua famiglia di tipi sta implementando matematicamente a * b + c. Quella menzione di "divisione" significa essere "addizione"?
Potato44

tra l'altro, ti capita di violare le tue specifiche al momento. "implementa una classe che moltiplica due numeri Peano" Ciò che hai attualmente non è una classe, ma sembra essere di tipo Constraint. Quindi è necessario aggiornare le specifiche o ripristinare il modulo che utilizza una classe anziché un sinonimo di tipo. Se dovessi usare il sinonimo di tipo potrei ottenere la mia risposta fino a 96 byte, quindi mi fa risparmiare un byte in più di te
Potato44

@ Potato44 Avevo l'impressione che una classe fosse solo qualcosa con un tipo che si traduce in una contrapposizione. Forse ciò era dovuto alla mancanza di chiarezza nella domanda. Allora tornerò alla mia risposta 115.
Ad Hoc Garf Hunter,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.