5
\$\begingroup\$

I'm a C# developer learning F#. I have a lot of questions about practices and principles within the code. I would like to know what a "native" F# developer would've done differently.

[<AutoOpen>]
module MyApp

module Railway = 

   // NOTE: will Tee make sense to most people?  it's from this https://fsharpforfunandprofit.com/rop/      
   let Tee func value = 
      func value
      value

module Stack =

   // NOTE: I've just made functions to give me stack-like behavior.  should i make
   //       a whole type for this?  what might it look like?
   // NOTE: Upper or lower case function names?  the built-in collections
   //       all have lower, but i read that shouldn't be practiced anymore.
   //       can't remember where.  any thoughts?
   let Push stack value = 
      value :: stack

   let Pop stack = 
      match stack with
      | []  -> None,  stack
      | [v] -> Some(v), []
      | _   -> Some(List.head stack), List.tail stack

[<AutoOpen>]
module State =

   open System.Text

   type State = { 
      Index : int
      Pointer : int   
      Stack : int list
      Input : byte list
      Memory : byte array
      }

   // NOTE: no one is forced to use this constructor function.  what if they
   //       initialize their own "state" improperly?
   let Create (input : string) = 
      {  Index = 0
         Pointer = 0
         Stack= List.empty
         Input = ASCIIEncoding.ASCII.GetBytes(input) |> Array.toList 
         Memory = Array.init 100  (fun i -> 0uy) }

[<AutoOpen>]
module Source =

   type Instruction = 
      | INCPTR
      | DECPTR
      | INCVAL
      | DECVAL
      | OUTVAL
      | INPVAL
      | WHILE
      | WEND
      | NOOP

   // NOTE: is this translation worth it?  i'm an enterprise developer at heart.
   //       defining all the things always feels good, but it does add more code
   //       to the mix.
   let Compile (code : string) : Instruction list =
      seq {
         for c in code do
            match c with
            | '>' -> yield INCPTR
            | '<' -> yield DECPTR
            | '+' -> yield INCVAL
            | '-' -> yield DECVAL
            | '.' -> yield OUTVAL
            | ',' -> yield INPVAL
            | '[' -> yield WHILE
            | ']' -> yield WEND         
            | _   -> yield NOOP
            }
      |> Seq.toList

module Brainfart =

   open System.Text
   open System

   // NOTE: have a function for these one-liners for consistency?  or inline it?
   let IncPtr (state : State) : State =
      {state with Pointer = state.Pointer + 1}

   let DecPtr (state : State) : State =
      {state with Pointer = state.Pointer - 1}

   let IncVal (state : State) : Unit =
      // NOTE: I'm mutating the state of the incoming variable, then this has the appropriate optimization effect, 
      //       but my state is only mostly immutable. is there some other way to structure this that is more 
      //       natural in F#?
      state.Memory.[state.Pointer] <- (state.Memory.[state.Pointer] + 1uy)

   let DecVal (state : State) : Unit =
      state.Memory.[state.Pointer] <- (state.Memory.[state.Pointer] - 1uy)

   let OutVal (state : State) : Unit =      
      state.Memory.[state.Pointer] 
      |> Array.singleton 
      |> ASCIIEncoding.ASCII.GetString
      |> printf "%s" 
      |> ignore

   let InpVal (state : State) : State = 
      // NOTE: this is a two step operation.  any way to make this nicer?
      let o, i = Stack.Pop state.Input
      match o with
      | Some v -> state.Memory.[state.Pointer] <- v
      | None   -> state.Memory.[state.Pointer] <- 0uy   
      {state with Input = i}      

   let While (state : State) : State = 
      {state with Stack = state.Index :: state.Stack}

   let Wend (state : State) : State = 
      if state.Memory.[state.Pointer] <> 0uy then 
         {state with Index = List.head state.Stack} 
      else 
         // NOTE: this is a two step operation.  any way to make this nicer?
         let o, s = Stack.Pop state.Stack
         match o with
         // NOTE: i don't use this variable.  any way to not declare it?
         | Some v -> {state with Stack = s} 
         | None -> state

   let IncIndex (state : State) : State =
      {state with Index = state.Index + 1}

   let RunInstruction (instruction : Instruction) (state : State) : State =
      match instruction with
      | INCPTR -> IncPtr state
      | DECPTR -> DecPtr state      
      | INCVAL -> Railway.Tee IncVal state
      | DECVAL -> Railway.Tee DecVal state      
      | OUTVAL -> Railway.Tee OutVal state      
      | INPVAL -> InpVal state      
      | WHILE  -> While state
      | WEND   -> Wend state
      | NOOP   -> state
      // NOTE: most of the operations above make a new copy of the state and IncIndex
      //       doubles-down on that.  i had the increments inline for each operation
      //       but felt like it was too much copied code.  any thoughts?  it is nicer
      //       to look at this way.
      |> IncIndex 

   let rec RunInstructions (code : Instruction list) (state : State) : State =
      if state.Index = code.Length then
         state
      else
        state |> RunInstruction code.[state.Index] |> RunInstructions code

   // NOTE: i get very self-conscious about explicitly type signatures, but they
   //       are very useful to me.  they help with intellisense and at-a-glance
   //       "what does this method do" stuff.  should i work on getting away from
   //       doing this?
   let Run (input : string) (code : Instruction list) : State =
      let f = printfn "" 
      State.Create input 
      |> RunInstructions code      
      // NOTE: any nicer way to do this?  
      |> Railway.Tee (fun s -> printfn "") 

Here's the fsx script I use for the "Hello World" program.

Source.Compile "++++++++++[>+++++++>++++++++++>+++>++++<<<<-]>++.>+.+++++++..+++.>>++++.<++.<++++++++.--------.+++.------.--------.>+." 

|> Brainfart.Run ""

EDIT

I recognize have a do/while in my code vs a while/do and that i need to change it. I also have a bunch of other questions in the code that are of interest to me. I'd appreciate any insight anyone can give.

\$\endgroup\$
  • \$\begingroup\$ For future reference: Test your interpretation implementation on FizzBuzz in Brainfuck if that doesn't print FizzBuzz, you've done something wrong. \$\endgroup\$ – Simon Forsberg Oct 30 '17 at 19:47
4
\$\begingroup\$
let While (state : State) : State = 
   {state with Stack = state.Index :: state.Stack}

let Wend (state : State) : State = 
   if state.Memory.[state.Pointer] <> 0uy then 
      {state with Index = List.head state.Stack} 
   else 
      // NOTE: this is a two step operation.  any way to make this nicer?
      let o, s = Stack.Pop state.Stack
      match o with
      // NOTE: i don't use this variable.  any way to not declare it?
      | Some v -> {state with Stack = s} 
      | None -> state

The While handler has no conditional logic, which is weird… and wrong. Instead, the conditional logic is in Wend. You have essentially committed the same mistake as in this implementation.

So, Brainfart is indeed an appropriate name for your module. =)

\$\endgroup\$
  • \$\begingroup\$ is this a semantics argument or something deeper? would calling them "Do" and "Until" square it away? will the system behave differently if i make the changes you suggest? \$\endgroup\$ – TBone Oct 26 '17 at 19:57
  • \$\begingroup\$ nm. you are correct sir. thanks for the input. \$\endgroup\$ – TBone Oct 26 '17 at 20:08

Your Answer

By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.