Awesome! Been wanting a good Elixir Zipper library, the others I'd found were for Erlang and had different usages. :-)
Easy to do it yourself though of course, but libraries also help document usage instead of just using a {[],[]} as a zipper. :-) On Monday, August 29, 2016 at 5:49:10 AM UTC-6, Brian Bugh wrote: > > Thanks for the great idea! I forgot about zippers. This was exactly what I > needed. There was a zipper tree library on hex but it was overkill for what > I needed, so I made my own: https://hex.pm/packages/zipper_list > > On Wednesday, August 24, 2016 at 10:00:29 AM UTC-5, OvermindDL1 wrote: >> >> Instead of holding 'data' and a 'dataptr' you could make the `data` data >> structure a zipper where the location of the zipper is the ptr. Would be >> very efficient. >> >> https://en.wikipedia.org/wiki/Zipper_(data_structure) >> >> There might even be a library or two on it on hex.pm. :-) >> >> >> On Tuesday, August 23, 2016 at 5:31:11 PM UTC-6, Brian Bugh wrote: >>> >>> Ah you're right! Very clever. That's awesome, thanks for explaining. >>> >>> The implementation I posted was doing the < and > commands backwards, >>> because I was thinking about it being cheaper to prepend to lists instead >>> of appending, but there's no way around it. >>> >>> The Wikipedia article about BF isn't very helpful. I've written an >>> interpreter successfully in Ruby, but I'm doing a clean room implementation >>> of it in Elixir so I can't "cheat". >>> >>> The < and > commands assume you have a data pointer list of infinite >>> size, 0 based. Something like Array.new(n, 0) in Ruby. So if you run a BF >>> program like *<<<<<<+*, you'd end up with a data structure of [1, 0, 0, >>> 0, 0, 0], hence the required list manipulation. >>> >>> - Brian >>> >>> On Aug 23, 2016, at 4:24 PM, Torben Hoffmann <torben...@gmail.com> >>> wrote: >>> >>> Hi Brian, >>> >>> Looking at what your code does and what I suggested I don't see a >>> difference. >>> >>> Your command/2 will subtract 1 from dataprt and if dataptr < 0 then you >>> will reset dataptr to 0 and pop data. >>> >>> My solution took the liberty of saying "if dataptr=0 (before you >>> subtract) we have to pop data otherwise we subtract one from dataprt and >>> that is it". >>> That should do the same as your code, because if dataptr=0 when you run >>> your command/2 function you will end up with dataptr=-1 before calling >>> do_trim, which will reset it to 0 and pop data. >>> >>> Reading the Brianfuck page on Wikipedia seems to suggest that the < >>> command should only decrement the dataptr, so I am not sure what the pop >>> you are doing with tl is good for. But then again, Brainfuck is not >>> supposed to be easy to understand... >>> >>> I actually can't believe that I am going to write the following... ;-) >>> >>> I would consider using two lists to hold the array of cells. Say, left >>> and right fields in your state. >>> Initially we would have left=[] and right= "a bunch of zeros". >>> Then we get something like this >>> >>> def command("<", state = %{left: []}) do >>> state # we silently do nothing when moving too far >>> end >>> >>> def command("<", state = %{left: [h|t]} do >>> %{state | left: t, right: [h | state.right]} >>> end >>> >>> # we always leave one element in right as that is the end of our world >>> def command(">", state = %{right: [_]} do >>> state >>> end >>> >>> def command(">", state = %{right: [h|t]}) do >>> %{state | left: [h | state.left], right: t} >>> end >>> >>> def command("+", state = %{right: [h|t]}) do >>> %{state | right: [ h+1 | t ]} # should probably do some overflow >>> handling if we want to keep having a cell as a byte >>> end >>> ... >>> def command("[", state = %{right: [0|t]}) do >>> state >>> end >>> >>> def command("[", state) do >>> # this is where the fun starts, now we need to enter a mode where we >>> loop the code >>> # until we have balanced out [ and ]. >>> ... >>> end >>> >>> And already here I can see that using command with just two arguments >>> will probably be hurtful in the long run. >>> Perhaps the function should be called interpret and take a list of chars >>> as the first argument and simply peel off the first element. >>> When we reach a loop we have to start accumulating the commands in case >>> the loop has to run more than once. >>> But if we call a loop function that executes commands and return the >>> collected loop body as well as an updated state, then we simply have to >>> check the value at the data pointer when the loop has been collected. If it >>> is zero we throw away the collected program, if not we re-insert the >>> collected program in front of the rest of the program and call interpret >>> again. >>> >>> Now I am almost tempted to go write the full interpreter, but luckily >>> for me it is late at my end right now, so I'll have to see if I am up for >>> it tomorrow!! >>> >>> Cheers, >>> Torben >>> >>> >>> On 23 August 2016 at 15:55, Brian Bugh <brai...@gmail.com> wrote: >>> >>>> It turns out that I think my assumption about how the BF interpreter >>>> works is wrong, but I want to continue this discussion because while my >>>> interpreter may be bad, I still don't know the answer to my Elixir >>>> question. :) >>>> >>>> Torben - thanks for the idea! I did try it with function pattern >>>> matching. However, the actual command is dataptr - 1 , so that needs >>>> to run first. *Then* if the dataptr is less than 0, the other check >>>> runs. The way you've suggested it in the first command assumes that >>>> the < command has already been run. :) >>>> >>>> Here's what I tried with pattern matching, which is even less >>>> comprehensible than that simple little if statement at the top. >>>> >>>> def command("<", state = %{data: data, dataptr: dataptr}) when >>>> is_list(data) and is_integer(dataptr) and dataptr >= 0 do >>>> do_trim(%{state | dataptr: dataptr - 1}) >>>> end >>>> >>>> def do_trim(state = %{data: data, dataptr: dataptr}) when dataptr < 0 >>>> do >>>> %{state | dataptr: 0, data: tl(data) } >>>> end >>>> >>>> def do_trim(state = %{data: data, dataptr: dataptr}), do: state >>>> >>>> Having been a programmer for 23 years, I'm a huge fan of writing code >>>> that my tired, burned out, >>>> working-on-the-weekend-because-release-date-is-Monday future self won't >>>> have to think about too hard to understand. >>>> >>>> Nothing I've come up with so far is as clear as that simple little if >>>> statement. >>>> >>>> >>>> >>>> On Tuesday, August 23, 2016 at 8:24:51 AM UTC-5, Torben Hoffmann wrote: >>>>> >>>>> Hi Brian, >>>>> >>>>> How about this? >>>>> >>>>> def command("<", state= %{dataprt: 0}) do >>>>> {:ok, %{state | data: tl(data)}} >>>>> end >>>>> >>>>> def command("<", state) do >>>>> {:ok, %{state | dataprt: state.dataptr - 1}} >>>>> end >>>>> >>>>> By using the pattern matching in the function clauses you get code >>>>> that reads like your description of the problem. >>>>> I.e., if the dataptr is zero pop the data stack otherwise decrement >>>>> the dataptr. >>>>> >>>>> Cheers, >>>>> Torben >>>>> >>>>> On 23 August 2016 at 15:08, Brian Bugh <brai...@gmail.com> wrote: >>>>> >>>>>> Here's another ugly one I came up with (with the complete function >>>>>> for reference) that is even worse. >>>>>> >>>>>> def command("<", state = %{data: data, dataptr: dataptr}) when >>>>>> is_list(data) and is_integer(dataptr) and dataptr >= 0 do >>>>>> dataptr = dataptr - 1 >>>>>> >>>>>> state = case state do >>>>>> %{dataptr: dataptr} when dataptr < 0 -> %{state | dataptr: 0, >>>>>> data: tl(data) } >>>>>> _ -> state >>>>>> end >>>>>> >>>>>> {:ok, %{state | dataptr: dataptr, data: data}} >>>>>> end >>>>>> >>>>>> Usually when I get stuck like this it means I'm overthinking >>>>>> something. >>>>>> >>>>>> Any suggestions? >>>>>> >>>>>> >>>>>> On Tuesday, August 23, 2016 at 7:51:22 AM UTC-5, Brian Bugh wrote: >>>>>>> >>>>>>> For fun and profit, I am writing a Brainf**k interpreter in Elixir. >>>>>>> >>>>>>> In one particular case, a data pointer should be decremented, and if >>>>>>> it's less than 0, it should be set to 0 and the top of the data stack >>>>>>> should be popped off. >>>>>>> >>>>>>> I assumed that I should write something like this (which passes my : >>>>>>> >>>>>>> dataptr = dataptr - 1 >>>>>>> >>>>>>> if dataptr < 0 do >>>>>>> dataptr = 0 >>>>>>> data = tl data >>>>>>> end >>>>>>> >>>>>>> but I get a compiler warning when I do this. It suggested that I use >>>>>>> the assignment form of *if*, which is fine, but I can't find an >>>>>>> elegant way to write the code now. This is what I came up with, which >>>>>>> seems >>>>>>> uglier and unnecessarily verbose for future readers. The first form >>>>>>> above >>>>>>> is much more readable. >>>>>>> >>>>>>> [dataptr | data] = if dataptr < 0 do >>>>>>> [0 | tl data] >>>>>>> else >>>>>>> [dataptr | data] >>>>>>> end >>>>>>> >>>>>>> Is there a better Elixir-way to write this? >>>>>>> >>>>>> -- >>>>>> You received this message because you are subscribed to the Google >>>>>> Groups "elixir-lang-talk" group. >>>>>> To unsubscribe from this group and stop receiving emails from it, >>>>>> send an email to elixir-lang-ta...@googlegroups.com. >>>>>> To view this discussion on the web visit >>>>>> https://groups.google.com/d/msgid/elixir-lang-talk/bfcb8bdf-aede-4955-ac50-a6ac6dde3e5a%40googlegroups.com >>>>>> >>>>>> <https://groups.google.com/d/msgid/elixir-lang-talk/bfcb8bdf-aede-4955-ac50-a6ac6dde3e5a%40googlegroups.com?utm_medium=email&utm_source=footer> >>>>>> . >>>>>> >>>>>> For more options, visit https://groups.google.com/d/optout. >>>>>> >>>>> >>>>> >>>>> >>>>> -- >>>>> http://www.linkedin.com/in/torbenhoffmann >>>>> @LeHoff >>>>> >>>> -- >>>> You received this message because you are subscribed to the Google >>>> Groups "elixir-lang-talk" group. >>>> To unsubscribe from this group and stop receiving emails from it, send >>>> an email to elixir-lang-ta...@googlegroups.com. >>>> To view this discussion on the web visit >>>> https://groups.google.com/d/msgid/elixir-lang-talk/743c6c3e-0def-4cf6-8dee-2338a19c0ef6%40googlegroups.com >>>> >>>> <https://groups.google.com/d/msgid/elixir-lang-talk/743c6c3e-0def-4cf6-8dee-2338a19c0ef6%40googlegroups.com?utm_medium=email&utm_source=footer> >>>> . >>>> >>>> For more options, visit https://groups.google.com/d/optout. >>>> >>> >>> >>> >>> -- >>> http://www.linkedin.com/in/torbenhoffmann >>> @LeHoff >>> >>> -- >>> You received this message because you are subscribed to a topic in the >>> Google Groups "elixir-lang-talk" group. >>> To unsubscribe from this topic, visit >>> https://groups.google.com/d/topic/elixir-lang-talk/_BPxgTc4VGM/unsubscribe >>> . >>> To unsubscribe from this group and all its topics, send an email to >>> elixir-lang-ta...@googlegroups.com. >>> To view this discussion on the web visit >>> https://groups.google.com/d/msgid/elixir-lang-talk/CABf3pCmnvMWt3xqvOC3Yim06XpLoSRkqzYqjPUoSp1HurGQbuw%40mail.gmail.com >>> >>> <https://groups.google.com/d/msgid/elixir-lang-talk/CABf3pCmnvMWt3xqvOC3Yim06XpLoSRkqzYqjPUoSp1HurGQbuw%40mail.gmail.com?utm_medium=email&utm_source=footer> >>> . >>> For more options, visit https://groups.google.com/d/optout. >>> >>> -- You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group. To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-talk+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-talk/5e31149b-e70c-431a-9522-1a7b9f9d70c8%40googlegroups.com. For more options, visit https://groups.google.com/d/optout.