Archivi Categorie: Linguaggi

Linguaggi di programmazione

Sweet, il Lisp e le parentesi

Io Twitter lo lovvo, assay ❤️ e quando trovo roba che m’ispira poi ne scrivo qui sul blog. Come il recente post Ricorsività e specialità.

Devo vedere Sweet concludevo e allora ecco cosa ho trovato.

La home di Sweet su SourceForge lo presenta come Readable Lisp/S-expressions with infix, functions, and indentation, brought to you by dwheeler.

Uh! chi è l’autore? Eccolo: David A. Wheeler, trovato via Twitter dove posta poco ma rockz! 💥

Le caratteristiche del progetto sono:

  • Infix notation, e.g., {3 + 4} in addition to (+ 3 4)
  • Optional traditional function notation, e.g., f(...) in addition to (f ...)
  • Optional indentation support greatly reduces the need for parentheses
  • Works on any data or program (it is general and homoiconic)

Poi c’è la pagina della Wiki, dice tutto (ahemmm…) e riporta un esempio molto simile a quello di Michael da cui sono partito nel post precedente.

La versione ortodossa della funzione fattoriale:

(define (factorial n)
  (if (<= n 1)
    1
    (* n (factorial (- n 1)))))

e quella Sweet:

define factorial(n)
  if {n <= 1}
    1
    {n * factorial{n - 1}}

Tolte tutte le parentesi tonde tranne quelle per il passaggio degli argomenti. Uso delle graffe per la notazione infissa.

È più leggibile? forse, dipende dall’abitudine.
Personalmente penso a come costruisco una funzione Lisp (in realtà per me Racket) dal di dentro, le parentesi le apro sempre in copia, così () e sulla carta. Poi nell’ambiente DrRacket, ma anche tanti altri editors, viene indicata la corrispondente aperta/chiusa.

Anche sulla notazione infissa rispetto alla prefissa sono indeciso:

ecco, non è la notazione cui siamo abituati, quella che ci insegnano nella scuola; invece:

😉 Che poi è questione di convenzione, esempio:

e

e anche

Ammetto che dc oggi è solo una provocazione. O no? le calcolatrici HP, la mitica 11c.

Concordo con Jesse Alama, qui (non ho trovato il cinguettio cui si riferisce), e qui, con un opportuno adattamento: sed 's/God/FSM/'.

Annunci

Calcoli al volo

when you need a calculator when working on the command line, what program do you open?” ci chiede Philip Guo, qui.

Uh! intrigante 💥😁 Prima di dire cosa uso io sento le risposte, prevedo cose impreviste 👽 e interessanti 😁

Jphn Regehr va sul difficile gdb, il debugger di Gnome.

Mate Soos dice IPython (mi sa che devo approfondire) e bc (anche questo da approfondire) dentro gli scripts bash.

Hal Daumé III dice, oltre a IPython, ghci. Uh! Haskell:

* cl-calc $ gi
GHCi, version 8.0.2: http://www.haskell.org/ghc/  :? for help
Prelude> 3 + 5
8
Prelude> 3 + (4 * 5)
23
Prelude> 3 + 4/3 * 5
9.666666666666666

gi è un alias personale, da über-pigrolazy:

* cl-calc $ alias gi
alias gi='ghci'

Funziona, devo lanciare GHCi, non posso (o non so come fare) a eseguirlo direttamente nel terminale. Per IPython c’è una dritta di Vegard Stikbakke sulla configurazione da valutare se si prevede di adottarlo.

Rich Felker (di cui dovrei approfondire la conoscenza) va direttamente con bash, senza trascurare FTW. OT ma ci cuole, imho: recentemente mi hanno suggerito Terminologia etc. utile anche in questo caso.

* cl-calc $ echo $(( 3 + 5 ))
8
* cl-calc $ echo $(( 3 + (4*5) ))
23
* cl-calc $ echo $(( 3 + 4 * 5 ))
23
* cl-calc $ echo $(( 3 + 4/3 * 5 ))
8

OOPS! non funziona la divisione. Cioè si ma solo per interi con troncamento quindi 4/3 vale 1.

Anticamente (diciamo 50 anni fa) il problema era noto e aveva una soluzione empirica, lavorare sui centesimi, ne parla anche Rich. Vediamo se mi ricordo come facevo:

* cl-calc $ echo $(( 7 / 3 ))
2

è impreciso, opero così:

* cl-calc $ echo $(( 700 / 3 ))
233

da leggersi come 2.33 come 700 stava per 7; opero sui centesimi; casi simili sono quelli dei decimi, millesimi e altri a piacere. OK, il nome corretto è “a virgola fissa” (se ricordo bene, è passato un po’ di tempo (e di moda)).

C’è chi ha sempre Emacs aperto p.es. Daniel Dunbar. Sì, la soluzione per quelli che hanno visto la luce (almeno limitatamente al ‘puter) e sanno che Emacs è un OS, non un editor.

Per altri c’è Google

Chris Palmer (uno da approfondire) è per expr, una versione sua modificata che purtroppo non ci mostra. La versione bare ha l’usuale problema di dover escapare i caratteri speciali:

* cl-calc $ expr 3 + 5
8
* cl-calc $ expr 3 + 5 * 4
expr: errore di sintassi
* cl-calc $ expr "3 + 5 * 4"
3 + 5 * 4
* cl-calc $ expr 3 + 5 \* 4
23

e vale solo per gli interi:

* cl-calc $ expr 3 + 5 \* 4/3
expr: argomento non intero

Altre risposte, c’è chi usa Octave, chi questa:

Brendan Dolan-Gavitt (uh! 👽) è un nerd serio:

* cl-calc $ awk 'END { print sqrt(2) }' /dev/null
1.41421
* cl-calc $ awk 'END { print 2 + 3 * 4  }' /dev/null
14
* cl-calc $ awk 'END { print 2 + 3 * 4/5  }' /dev/null
4.4

Contrariamente a quel che ne pensa il prof (sì è prof) il metodo potrebbe essere sviluppato.

Salto chi dice cose com R o CL. Stephen Checkoway (RTs of cats are endorsements) propone la costruzione di una funzione (richiede Perl) e un alias per lanciarla. Funziona, eccone una versione semplificata, in forma di script (sc-ev):

#!/bin/bash

eval_helper() {
    bc -ql <<< "$@"| perl -pe 's/(\.[^0]+)0+$|\.0+$/$1/'
}

eval_helper "$@"
* cl-calc $ bash sc-ev "12 - 2 * 3"
6
* cl-calc $ bash sc-ev "12 - 2 / 3"
11.33333333333333333334

Stephen usa anche calc, devo parlarne.

Kristopher Micinski (anche lui prof, anche lui da approfondire) sgrida un po’ tutti, anche me suppongo.

Salto risposte varie, sensate e no, imho e arrivo a Sam King: dc, but seems like I should check out bc.

* cl-calc $ dc -e "2k 3 4 5 / + p"
3.80

I macisti hanno i loro tools, salto. C’è chi usa irb (Ruby), MatLab (versione onero$a di Octave), R (già detto) e Python.

* cl-calc $ py3 -c "print(3 + 5)"
8
* cl-calc $ py3 -c "print(3 + 5/2)"
5.5

sì ho l’alias (anche se in questo caso non serve):

* cl-calc $ alias py3
alias py3='python3 -q'

Noto che nessuno ha nominato Excel (è, per qualcuno, quasi un OS, come Emacs) merito di Twitter che ti consente di scegliere i tweeps giusti e con il passaparola il loro numero tende a crescere esponenzialmente.

La mia soluzione? calc.

* cl-calc $ calc "3 + 4 * 5 / 6"
	6.33333333333333333333
* cl-calc $ calc "pi = 4 * atan(1); 2 * pi * 10"
	62.8318530717958647696
* cl-calc $ calc "pi = 4 * atan(1); pi * 10^2"
	314.159265358979323848

Le virgolette? non sono necessarie se non si lasciano spazi:

* cl-calc $ calc 4 * 5
Missing operator

Error in commands
* cl-calc $ calc 4*5
	20

Ricorsività e specialità

Dell’ottimo Michael Burge 💥 non so molto; lo si trova su Twitter, ha un blog pieno di cose troppo nerdose per me. È nuovo, giovane immagino e per me un maestro. Ecco un suo tweet, muto, cioè subito via con il codice.

Ma funzionerà? mi sembra troppo semplice, forse è meta…

Non resta che provare

* rec $ ghci
GHCi, version 8.0.2: http://www.haskell.org/ghc/  :? for help
Prelude> fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
Prelude> fibs !! 8
21
Prelude> fibs !! 12
144
Prelude> fibs !! 0
0

La prima riga definisce la funzione fibs che crea la lista infinita dei numeri di Fibonacci fornendo i valori dei primi 2 e definendo il valore del successivo come (+) dei valori della lista stessa. !! estrae l’elemento indicato nella lista. Semplice vero? l’ho capita anch’io. E siccome Haskell è lazy –OK, non voglio ripetermi.

OK, ma io preferisco Racket allora ecco. È comodo eseguirlo nel suo ambiente di sviluppo, DrRacket, perché devo caricare il linguaggio lazy:

Ma io sono della vecchia scuola, quella della REPL e allora ecco…

* rec $ racket
Welcome to Racket v6.11.
> (require lazy)
> (define fibs (cons 0 (cons 1 (map + fibs (rest fibs)))))
> (displayln (list-ref fibs 8))
21
> ^D
* rec $

OK. È la tradudione letterale del programma Haskell. Più verboso. Ma Michael (lo sapete che si è fatto un linguaggio tutto suo, Pyramid) non si ferma qui, va oltre. Per procedere devo acquisire il modulo sweet-exp e qui è comodo davvero DrRacket operando da menu: File/Install Package...

Fatto e allora (questa volta la REPL non basta):

Nota: il tweet contiene un errore, manca la parentesi aperta prima di +.

Nota per me: devo informarmi su Sweet, non sono l’unico anche Jesse 💥

Come se non bastasse ecco William 💥

Sì, avendo tempo ci sarebbe da vedere anche Cur.

TIO – try it online

La serendipità 😁 Capita che devi convertire uno script (scritto in Basic per Windows) in Python (per Windows e Linux, funziona ovunque) quando mancano le conoscenze (ahemmm… dimenticato; come passa il tempo!) e l’autore originale non è più qui e non si trova e allora googli disperatamente, chissà…

Vero che c’è repl.it ma trovato TIO.

TIO is a family of online interpreters for an evergrowing list of practical and recreational programming languages.
[…]
TIO hosts 195 practical and 316 recreational programming languages, for a total of 511 languages.

Da provare. Poi in realtà nel  nostro  mio caso l’evoluzione è stata diversa ma –come detto– TIO è da provare. Non con tutti i linguaggi, un paio (abbondante) può bastare, anzi l’interesse è per uno solo: il Basic.

A seguito di un approfondito brainstorming (t > 1 min.) la scelta è caduta sul crivello di Eratostene. Invece di scrivere il codice, non che non sia alla mia portata ma la laziness ereditata con Haskell, sono ricorso a Rosetta Code.

TIO funziona perfettamente, almeno per script non troppo grandi. I test effettuati li riporto con qualche nota. Inizio con Python 3:

def eratosthenes2(n):
    multiples = set()
    for i in range(2, n+1):
        if i not in multiples:
            yield i
            multiples.update(range(i*i, n+1, i))

print(list(eratosthenes2(100)))

Inserito il codice in Code e lanciato con il pulsante Execute the program nel campo Output è comparso immediatamente il risultato

[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

Le indicazioni del campo Debug mi informano che l’esecuzione è avvenuta sotto time in ambiente Linux (credo, almeno da me sarebbe così) con numeri troppo piccoli per avere qualche significato. Vero che il problema era molto piccolo.

Io vengo da lontano (come viaggiatore nel tempo) e visto che Rosetta e TIO propongono il Fortran (90, ma per il 77 basta cambiare l’indentazione e modificare le dichiarazioni iniziali):

program sieve

  implicit none
  integer, parameter :: i_max = 100
  integer :: i
  logical, dimension (i_max) :: is_prime

  is_prime = .true.
  is_prime (1) = .false.
  do i = 2, int (sqrt (real (i_max)))
    if (is_prime (i)) is_prime (i * i : i_max : i) = .false.
  end do
  do i = 1, i_max
    if (is_prime (i)) write (*, '(i0, 1x)', advance = 'no') i
  end do
  write (*, *)

end program sieve

ottenendo

2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

Ma si cercava per il Basic. Rosetta ha diversi codici per questo linguaggio che ha avuto un’evoluzione maggiore di qualunque altro, Lisp compreso. È finito che ne ho modificato una versione sensata, con un bug mysteryoso. Cioè essere partito con il Fortran ho considerato che l’indice dell’array partisse da 1 anziché 0. In realtà anticamente per le versioni Microsoft c’era la direttiva option base 1, usata abitualmente da quelli come me. Il codice è risultato questo:

limit = 100
dim flags(101)
for n = 2 to sqr(limit)
  if flags(n) = 0 then
    for k = n * n to limit step n
      flags(k) = 1
    next k
  endif
next n

for n = 2 to limit
  if flags(n) = 0 then
    print n, " "
  endif
next n

Tutto OK 😋

Per finire visto che avevamo (ero con Edo, un giovane molto promettente) sentito anche un capo, senior, anche lui partito con il Fortran ma poi finito su altre strade, da statistici. Insomma Rosetta e TIO forniscono J, evoluzione di APL e allora ecco:

sieve=:verb define
  seq=: 2+i.y-1  NB. 2 thru y
  n=. 2
  l=. #seq
  whilst. -.seq-:prev do.
     prev=. seq
     mask=. l{.1-(0{.~n-1),1}.l$n{.1
     seq=. seq * mask
     n=. {.((n-1)}.seq)-.0
  end.
  seq -. 0
)

Inserendo sieve 100 nel campo Input si ottiene –mysteryosamente– il risultato previsto.

Se dovessi dire la verità, solo la verità, tutta la verità Vostro Onore dovrei riportare altre prove ma il post diventerebbe troppo lungo e poi non sono pertinenti con il caso in esame. Piuttosto MY-BASIC… da indagare, prossimamente… forse… 👽

Bash, quant’è grande, quante cose no so

Ho già confessato più vole che io Julia Evans, b0rk, la lovvo, assay 😍 E se non fossi così timido (e vecchio) una dichiarazione come si usa sarebbe già partita da tempo (ma si usa via Twitter?).

Ho contagiato anche qualcuno dei miei tweeps, recentemente ne parlavo con un nerd, molto più bravo di me, ed ecco un riassunto di quanto ci siamo detti, relativamente a more bash tricks e less.

Cominico con less, per me è più facile. Quando ho iniziato a usare Unix non c’era e l’imprint di more dura tuttora. Ma –me lo dico spesso– devo aggiornarmi. Anche qui ci sono cose che non sapevo, per esempio l’opzione v che ti apre il documento che stai lessando nell’editor definito dalla fariabile d’ambiente $EDITOR: uh! la setto subito, per me export EDITOR=medit verso il fondo di ~/.bashrc.

Il post sui tricks (non so tradurlo in italiano: trucchi? i francesi usano astuces ma astuzie non mi sembra possa funzionare) è ancora più ispirativo.

<(...) confesso che non lo conoscevo; colpa mia, c’è nel manuale Bash Reference Manual, prima o poi lo devo leggere tutto.

3.5.6 Process Substitution

Process substitution is supported on systems that support named pipes (FIFOs) or the /dev/fd method of naming open files. It takes the form of

<(list)

or

>(list)

9.2 Bash History Builtins

Bash provides two builtin commands which manipulate the history list and history file.

fc

fc [-e ename] [-lnr] [first] [last]
fc -s [pat=rep] [command]

Vale quanto detto per $EDITOR ma per adesso mi sembra più semplice richiamare il comando precedente con il tasto freccia-su. E qui si potrebbe inserire tutta la tiritera della history. OT: come la mettiamo con l’apostrofo? la h è ‘taliana o inglisc?

Shellcheck l’avevo già sentita nominare ma mai affrontata. Provandola su qualche scripts ho visto che dovrei aggiungere una spruzzata di virgolette. Da approfondire, c’è il man che mi indica anche il sito (su GitHub, in questi giorni l’unico gioco in città).

I suggerimenti di Julia vanno spesso approfonditi, sono un punto di partenza ma chi ben comincia…

Una nota OT: l’immagine iniziale dei miei post non è mai pertinente; serve solo per Twitter, con un’immagine il post risulta più visibile.

L’istruzione global in Python

Aaron Meurer twitta un quiz, questo. Sembra facile, anzi troppo e essendo un quiz c’è chi –correttamente– risponde senza eseguire il codice. La maggioranza sbaglia (55%).

Adesso non vi dico come ho votato ma devo confessare che avevo già affrontato un caso simile (c’era di mezzo il Basic, non sono riuscito a ritrovarlo, peccato). Ma mi va di ravanare l’argomento. Intanto la versione originale:

# aam.py

a = 1

def maybe_inc_a(inc):
  if inc:
    global a
  a += 1

maybe_inc_a(False)
print(a

OK, cioè no, inc è False quindi quindi il blocco if non viene eseguito, provo a commentarlo ed ecco:

# aam-c.py

a = 1

def maybe_inc_a(inc):
  # if inc:
  #   global a
  a += 1

maybe_inc_a(False)
print(a)

Errore, la soluzione la da Aaron (dopo un paio di giorni):

The global statement applies to the entire function regardless where it is.

E da anche un suggerimento:

The best practice is to put global declarations at the top of the function or right before the variable is first used.

Io che vengo dal Fortran sono per il top, chi è avvezzo a C/C++ preferisce molto probabilmente l’altra posizione (for int i...).

Tratta poi altri casi ma mi basta questo. Intanto, ripensandoci c’è un comportamento un po’ particolare, cosa succede con questo codice?

# aam-g.py

st = 'ciao'
a = 1

def maybe_inc_a(inc):
  print(st)
  print(a)

maybe_inc_a(False)

Ovviamente non è possibile modificare le variabili.

In questi casi diventa importante la documentazione. Cerco per global ed ecco:

The global statement is a declaration which holds for the entire current code block. It means that the listed identifiers are to be interpreted as globals. It would be impossible to assign to a global variable without global, although free variables may refer to globals without being declared global.

Names listed in a global statement must not be used in the same code block textually preceding that global statement.

Names listed in a global statement must not be defined as formal parameters or in a for loop control target, class definition, function definition, import statement, or variable annotation.

E subito sotto segue nonlocal, mai usato ma mai dire mai (uh! l’ho detto 3 volte in questa frase 😊).

In conclusione non ho detto niente più di Aaron ma metti che i miei amici pythonisti –colpevolmente– non lo seguano su Twitter…

Haskell 204 – OK ma…

L’ho tirata lunga con Haskell, in modo tutt’altro che razionale, colpa anche mia ma non solo. la pagina della documentazione del sito ufficiale comincia con libri per impaprare Haskell poi continua con Corsi, Tutorials e Risorse Online prima di arrivare a Manuali e Guide. Ecco, io ho seguito queste indicazioni con risultati non sempre soddisfacenti (a essere buono). Non sempre e non è colpa degli autori. Piuttosto Haskell, benché non sia un linguaggio nuovo, è tuttora in evoluzione, me ne sono accorto da tempo ma oggi aprendo la guida di GHC, qui, ne ho avuto una conferma terrificante.

La versione che ho installato

$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 8.0.2

è vecchia, il manuale si riferisce alla 8.4.1, da intendersi come 8.4.

Nella versione corrente ci sono parecchie novità (per me). Considerando che i tutorial che ho seguito non erano recenti mi spiego le difficoltà trovate nel codice che tentavo di riprodurre.

Dovrei ripartire aggiornando la versione, cioè installando l’ultima, quella cui si riferisce la guida e ripartire. Ma non me la sento anche perché me lo sento che poi cambia ancora.

Dall’esame del tutorial del prof. Brent Yorgey ho visto che riesco a comprendere il codice, anche se avrei a volte difficoltà a scriverne di mio, forse a causa della mia storia pregressa, non funzionale. Sono però convinto che centri anche la filosofia del linguaggio, io sono più lato Python, ma anche Lisp, cioè Scheme, cioè Racket –rockz 💥

C’è un linguaggio, gia visto brevemente in passato che mi attira, Rust. Ma anche lui, vedo su Twitter, che le versioni si rincorrono velocemente. Chissà… 😁

In questi giorni da me la connessione a Internet funziona in modo molto saltuario; sono in una zona marginale e la manutenzione è tutt’altro che ottimale, secondo le ultime notizie l’ispezione tecnica è prevista per il 4 giugno, probabilmente salvo ulteriori rinvii. Ne sto approfittando offline con Python, rockz 💥

Haskell – 203 – basta posts con questo tutorial

Continuo da qui, copio qui.

In this module, we will explore some rudiments of parsing. Naturalmente c’è il file literate.

Ci sono esercizi da fare, siccome sono über-lazy sbircio le soluzioni e sì, si potrei (dovrei) farli.

Però –ci sto pensando da giorni ormai e ho deciso– quest posts sul corso del prof Brent Yorgey (ottimo) me li devo vedere in privato, non sono raccontabili. Poi, se userò Haskell (e non è detto) ci ricorrerò quando mi verrò a trovare in ambasce. Il modulo 18 —Monads— mi ha chiarito cose, in fondo non era come sembrava 😁

Haskell – 202 – il linguaggio Arith: pretty printing

Continuo da qui, copio qui.

OK, ottimo prof. Yorgey 💥 Ma siccome sono solo, non in classe faccio a modo mio. Oltre al file literate carico le soluzioni trovate qui.

Lo so che non sarebbe corretto in classe, ma sono atipico (e discolo 👽).

L’esame del codice mi permette (credo sia la prima volta) di vedere un caso pratico (a dire il vero manca ancora qualcosa per renderlo eseguibile fuori da GHCi ma fa parte del gioco).

La mia impressione (solo mia, non esportabile) è che il codice risulta molto normale, ritiro le accuse di matematicità distribuite in passato.

E continuo con l’esame, post corto, lo so 😎

Haskell – 201 – sintassi e semantica

Continuo da qui, copio qui.

Si parte caricando il file literate.

Poi cominciano le difficoltà: la lezione è OK se sei presente fisicamente, con i compagni più smart e il prof. Io sono qui solo, vecchio e quindi meno aggiornato e condizionato da cattive abitudini (ultimamente bash oltre a Python) e alle volte (anzi molto spesso) non riesco a stare al passo con la classe.

Ma ce la metto tutta, per esempio:

Prelude> :l 04-syntax-semantics.lhs
[1 of 1] Compiling Main             ( 04-syntax-semantics.lhs, interpreted )
Ok, modules loaded: Main.
*Main> parseMirror "L.R"
(Layer Middle,"")
*Main> parseMirror "L.L"
*** Exception: 04-syntax-semantics.lhs:(155,5)-(156,42): Non-exhaustive patterns in case

*Main> parseMirror "LL.RR"
(Layer (Layer Middle),"")
*Main> parseMirror "LL.RR.LL"
(Layer (Layer Middle),".LL")

Poi arriva l’esercizio vero, BIN:

--bin.hs

{-# LANGUAGE GADTs #-}

import Data.Char

-- http://ozark.hendrix.edu/~yorgey/360/f16/modules/04-syntax-semantics.html

data Bin where
    Hash :: Bin
    Layer :: Bin -> Bin -> Bin
    deriving Show

prettyBin :: Bin -> String
prettyBin Hash = "#"
prettyBin (Layer x y) = "(" ++ prettyBin x ++ prettyBin y ++ ")"


parseBin :: String -> Maybe (Bin, String)
parseBin ('#' : rest) = Just (Hash, rest)
parseBin ('(' : rest) =
    case parseBin rest of
        Just (x, rest') -> case parseBin rest' of
            Just (y, ')' : rest'') -> Just (Layer x y, rest'')
            _ -> Nothing
        _ -> Nothing
parseBin _ = Nothing

interpBin :: Bin -> Integer
interpBin Hash = 1
interpBin (Layer l r) = interpBin l + 2 * (interpBin r)

evalBin :: String -> Maybe Integer
evalBin xs = do
    (bin, _) <- parseBin xs
    return $ interpBin bin

{-
   ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
 ::= '#'
        | 
        | '('   ')'
-}

data EBin where
    EHash :: EBin
    Digit :: Int -> EBin
    ELayer :: EBin -> EBin -> EBin
    deriving Show

prettyEBin :: EBin -> String
prettyEBin EHash = "#"
prettyEBin (Digit d) = show d
prettyEBin (ELayer x y) = "(" ++ prettyEBin x ++ prettyEBin y ++ ")"

parseEBin :: String -> Maybe (EBin, String)
parseEBin ('#' : rest) = Just (EHash, rest)
parseEBin ('(' : rest) =
    case parseEBin rest of
        Just (x, rest') -> case parseEBin rest' of
            Just (y, ')' : rest'') -> Just (ELayer x y, rest'')
            _ -> Nothing
        _ -> Nothing
parseEBin (c : rest) | isDigit c = Just (Digit $ read [c], rest)
                     | otherwise = Nothing
parseEBin _ = Nothing

desugar :: EBin -> Bin
desugar EHash = Hash
desugar (Digit d) = grow d where
    grow 0 = Hash
    grow n = let inner = grow (n-1) in Layer inner inner
desugar (ELayer l r) = Layer (desugar l) (desugar r)


evalEBin :: String -> Maybe Integer
evalEBin xs = do
    (ebin, _) <- parseEBin xs
    return $ interpBin $ desugar ebin
*Main> :l bin
[1 of 1] Compiling Main             ( bin.hs, interpreted )
Ok, modules loaded: Main.
*Main> evalEBin "(#2)"
Just 19
*Main> evalEBin "(#4) (#2)"
Just 163
*Main> evalEBin "((#4) (#2))"
Nothing
*Main> evalEBin "((#4)(#2))"
Just 201
*Main> evalEBin "0"
Just 1
*Main> evalEBin "1"
Just 3
*Main> evalEBin "2"
Just 9
*Main> evalEBin "#"
Just 1
*Main> evalEBin "##"
Just 1
*Main> evalEBin "(##)"
Just 3
*Main> evalEBin "((##) (##))"
Nothing
*Main> evalEBin "((##)(##))"
Just 9

Uhmmmmysteryousassay 😯

Hai copiato? Sì, qui.

Devo rivedere profondamente il modo con cui affronto il corso.