Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
Martijn Hoekstra
@martijnhoekstra
thanks
Otto Chrons
@ochrons
you need that scalatags import to bring in some implicit conversions, though
Martijn Hoekstra
@martijnhoekstra
fair enough
"printing" is something really weird anyway
Otto Chrons
@ochrons
btw, Cats 1.0.1 is already in ScalaFiddle if somebody wants to try it out ;)
Otto Chrons
@ochrons
probably not a Cats issue?
Fabio Labella
@SystemFw

@martijnhoekstra right, so there's a lot of directions you can go from there.

  • You could refactor the interpret bit, for example using the State monad (or its tagless version MonadState) rather than the manual state threading, or note how your Data and Instructions are very similar to each other.
  • You could switch to a different language (e.g. a stack based one where you can also pop from the stack), and note that what you have now, i.e. an instruction being an AST with no type parameters and a program being simply List[BFInstruction], transforms to an instruction being an AST with a type parameter for the output type, and a program no longer being just a List[Instruction], but a Free[Instruction, A], so you go from Free Monoids to Free Monads.
  • You could try moving what you have to final tagless, but as it stands, it's not easy: the current logic requires going back and forth along the List of instructions for loops, which is obviously easier to do if you have a concrete representation (the AST). If you change your instructions set to e.g. have a case class Loop(p: List[BFInstruction) instead, then you can remove that logic and move it to the Parser (you'd need a parser combinator library), at which point translating to tagless should be easier.

There are two versions that do the above:
1) initial: https://github.com/ekmett/lens/blob/b1ca6288fed878d7197416a9ca72983577260ad6/examples/Brainfuck.hs
2) final:
https://github.com/ekmett/lens/blob/bec00420db73cacb2bb8a277ca115d2220ef2c76/examples/BrainfuckFinal.hs

warning: the haskell there is a bit outdated

(I recommend reading the initial version first)
Martijn Hoekstra
@martijnhoekstra
thanks, I'll take a look where I can go
I particularly wanted to see where things would lead, and what kinds of operations are easy/hard in what style, but a stack based language might indeed be better for discovering that.
there are such obvious optimizations, that I was really wondering how that would work, and what that would look like
Fabio Labella
@SystemFw
I think writing "compilers" is skewed towards an initial representation
it can be more intuitive to write transformations and stuff
final can give you advantages like fusion though, but it's harder to figure out imho
the opposite is true in user code, like for your standard "interface"
where final wins due to less boilerplate and ease of composition
Martijn Hoekstra
@martijnhoekstra
I was led to believe that Free was ideal for such things
Fabio Labella
@SystemFw
yeah, that's what I mean by initial representation
an AST, basically
whereas a final representation is functions (the eliminators)
and in fact, you are using Free now
your Free is List
and it's Free wrt to Monoid, which is all you need here
Martijn Hoekstra
@martijnhoekstra
I don't know what initial, final, or eliminator really means
Fabio Labella
@SystemFw
ok, I can explain the terminology to bring some clarity :)
so, the domain all this originated is EDSLs (embedded domain specific languages)
the most intuitive way of writing one is by writing a data structure that represent your EDSL, then interpreting it into something else
e.g. data Op = Add Int Int | Const Int
plus a function from Op to whatever you want to interpret this to (e.g. an Int for evaluation, or a String for pretty printing)
this is an example of an initial encoding, also referred to as a deep embedding
what you had there is therefore initial
an AST basically
Martijn Hoekstra
@martijnhoekstra
so this translates to abstract class Op; case class Add(l: Int, r: Int) extends Op; case class Const(value: Int) extends Op right?
Fabio Labella
@SystemFw
yes
The Free monad is another example of this: your EDSL is "monadicity", and you represent that by a concrete data structure, which you then interpret to something else
there's a million other things to talk about on this, but I'll skip
conversely
a final encoding, or shallow embedding, is when you represent your mini language using functions, i.e. directly in terms of what are going to be the interpreters (the eliminators of your language/edsl/algebra)
for example
actually wait, I got the other embedding wrong (too easy)
let's say the initial one is:
data Exp = Add Exp Exp | Lit Int
sealed trait Exp
case class Add(l: Exp, r: Exp) extends Exp
case class Lit(i: Int) extends Exp
Fabio Labella
@SystemFw
so that we can nest expressions like Add(Lit(1), Add(Lit(3), Lit(3))
interpretation for this style is just a function that uses pattern matching and recursion
Martijn Hoekstra
@martijnhoekstra
yeah, SeemsGood
Fabio Labella
@SystemFw
the same thing in final style would be
class Exp t where
    lit :: Int -> t
    add :: t -> t -> t
trait Exp[T] {
  def lit(i: Int): T
  def add(l: T, r: T): T
}
so we're using typeclasses and, more generally, functions, to represent our expressions
what do these function represent? they represent the interpreters for each instruction
for example, let's see how to evaluate both styles to an Int
Martijn Hoekstra
@martijnhoekstra
top one would be def rec(exp: Exp): Int { exp match { case Add(l, r) => rec(l) + rec(r); case Lit(i) => i }}