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.
1 Answer 1
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. =)
-
\$\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– TBone2017年10月26日 19:57:30 +00:00Commented Oct 26, 2017 at 19:57