# Opportunity to write some idiomatic Julia code

**URL:** <https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702>\
**Category:** Community\
**Created:** [May 18, 2020, 4:14pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702 "2020-05-18T16:14:23Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![anon94023334](https://avatars.discourse-cdn.com/v4/letter/a/e274bd/32.png) [@anon94023334](https://discourse.julialang.org/u/anon94023334)\
**Post date:** [May 18, 2020, 4:14pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/1 "2020-05-18T16:14:23Z")

</div>

[This](https://github.com/isene/O6) came up in my RSS feed today. The Julia code could do with a makeover for anyone interested.

---

<div class="post-metadata">

**Author:** ![Palli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/palli/32/3380_2.png) [@Palli](https://discourse.julialang.org/u/Palli)\
**Post date:** [May 18, 2020, 7:16pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/2 "2020-05-18T19:16:45Z")

</div>

Thanks, but to others looking at this, note, the Julia code is incorrect. You often don’t see it until you do (and I’ve only seen a negative value from the Ruby code):

```julia
$ ~/julia-1.4.0/bin/julia O6.jl
ERROR: LoadError: UndefVarError: t not defined

Stacktrace:
 [1] top-level scope at /home/pharaldsson_sym/O6.jl:18
 [2] include(::Module, ::String) at ./Base.jl:377
 [3] exec_options(::Base.JLOptions) at ./client.jl:288
 [4] _start() at ./client.jl:484
in expression starting at /home/pharaldsson_sym/O6.jl:16

```

Maybe helpful to see why, in Julia 1.5 or:

```julia
$ julia-1.6-DEV O6.jl
┌ Warning: Assignment to `m` in soft scope is ambiguous because a global variable by the same name exists: `m` will be treated as a new local. Disambiguate by using `local m` to suppress this warning or `global m` to assign to the existing global variable.
└ @ ~/O6.jl:8
┌ Warning: Assignment to `mark` in soft scope is ambiguous because a global variable by the same name exists: `mark` will be treated as a new local. Disambiguate by using `local mark` to suppress this warning or `global mark` to assign to the existing global variable.
└ @ ~/O6.jl:9
┌ Warning: Assignment to `d` in soft scope is ambiguous because a global variable by the same name exists: `d` will be treated as a new local. Disambiguate by using `local d` to suppress this warning or `global d` to assign to the existing global variable.
└ @ ~/O6.jl:12
[..]

```

It’s good to know about the new soft scope rules (that only apply in the REPL, as of 1.5), while I’ve been using 1.5 (and 1.6) for a while now, I’m first now seeing these warnings, that I’m not sure how to switch off (-q doesn’t), nor non-REPL or include(“O6.jl”).

---

<div class="post-metadata">

**Author:** ![Ian\_Slagle](https://avatars.discourse-cdn.com/v4/letter/i/b5a626/32.png) [@Ian\_Slagle](https://discourse.julialang.org/u/Ian_Slagle)\
**Post date:** [May 18, 2020, 7:39pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/4 "2020-05-18T19:39:15Z")

</div>

```julia
D(x) = rand(1:x)

function D(x, roll)
    previous_roll = roll
    roll = D(x)
    return roll, previous_roll
end

function O(x)
    main_roll, previous_roll = D(x, 0)
    mark = ""
    if main_roll == 1
        roll, previous_roll = D(x, main_roll)
        while roll <= (x ÷ 2)
            main_roll -= 1
            roll, previous_roll = D(x, roll)
            if roll == 1 && previous_roll == 1
                mark = " fumble"
            end
        end
    elseif main_roll == x
        roll, previous_roll = D(x, main_roll)
        while roll >= (x ÷ 2 + 1)
            main_roll += 1
            roll, previous_roll = D(x, roll)
            if roll == x && previous_roll == x
                mark = " critical"
            end
        end
    end
    println("$(main_roll)$(mark)")
end

```

I think this correctly implements the O(6) roll, but how Julian it is, I don’t really know (I’m a bit new to the language). I’m also adding a histogram of the results, ignoring fumbles and crits.

![o6hist](https://global.discourse-cdn.com/julialang/original/3X/6/f/6fe81d5ccafb28a73dc90d17fc2a8bbb16ad37ac.png)

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [May 18, 2020, 8:04pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/5 "2020-05-18T20:04:11Z")

</div>

This seems a lot easier (the non-Julia codes are specialized to 6-sided die, which is the only case described in the problem, so it makes sense to specialize the Julia code as well), and you can unify the `6` and `1` loops:

```julia
function O6()
    total = rand(1:6)
    critical = false
    if total == 6 || total == 1
        Δ = total == 6 ? 1 : -1
        prev = 6
        while true
            die = rand(1:6)
            die ≤ 3 && break # 1-3 or 4-6 are equivalent for 6-sided die
            critical = critical || (prev == die == 6)
            prev = die
            total += Δ
        end
    end
    return total, critical ? (total > 6 ? "critical" : "fumble") : ""
end

```

With a little statistics knowledge, you could eliminate the loop entirely and write an expression (probably based on `randexp`) to directly generate an integer equivalent to the number of consecutive `true` values from a sequence of `rand(Bool)` calls.

---

<div class="post-metadata">

**Author:** ![Ian\_Slagle](https://avatars.discourse-cdn.com/v4/letter/i/b5a626/32.png) [@Ian\_Slagle](https://discourse.julialang.org/u/Ian_Slagle)\
**Post date:** [May 18, 2020, 8:06pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/6 "2020-05-18T20:06:32Z")

</div>

Yeah that last bit is what I’m working on now (though I think it might be easier to use rand(Geometric(0.5)). The trick is, I think, accounding for the marks (“fumble” and “crit”)

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [May 18, 2020, 8:19pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/7 "2020-05-18T20:19:18Z")

</div>

> [@Ian\_Slagle](#):
>
> The trick is, I think, accounding for the marks (“fumble” and “crit”)

Yes, I missed that we needed to track this in my first reading; I amended the code to check for that, but it makes that statistics a lot more complicated to model without a loop.

---

<div class="post-metadata">

**Author:** ![tkluck](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkluck/32/15769_2.png) [@tkluck](https://discourse.julialang.org/u/tkluck)\
**Post date:** [May 18, 2020, 8:24pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/8 "2020-05-18T20:24:09Z")

</div>

An entry for fewest lines:

```julia
julia> D(n) = rand(1:n)
D (generic function with 1 method)

julia> O6() = begin
           mark = nothing
           s = t = D(6)
           s == 1 && while ((t′, t) = (t, D(6)); t in 1:3)
               s -= 1
               1 == t′ == t && (mark = :fumble)
           end
           s == 6 && while ((t′, t) = (t, D(6)); t in 4:6)
               s += 1
               6 == t′ == t && (mark = :critical)
           end
           return s, mark
       end
O6 (generic function with 1 method)

```

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [May 18, 2020, 8:36pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/9 "2020-05-18T20:36:32Z")

</div>

> [@tkluck](#):
>
> An entry for fewest lines:

Here is an abbreviated version of your code that merges the two loops:

```julia
function O6()
    s, t, crit = rand(1:6), 1, false
    s in (1,6) && while ((t′, t) = (t, rand(1:6)); t < 4)
       s += s ≥ 6 ? 1 : -1
       crit |= 1 == t′ == t
    end
    return s, crit ? (s > 6 ? :critical : :fumble) : nothing
end

```

which is shorter than [the Forth code](https://github.com/isene/O6/blob/master/O6-forth.fs) while being (I think) more readable.

---

<div class="post-metadata">

**Author:** ![kernelmethod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kernelmethod/32/35480_2.png) [@kernelmethod](https://discourse.julialang.org/u/kernelmethod)\
**Post date:** [May 18, 2020, 8:50pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/10 "2020-05-18T20:50:46Z")

</div>

My entry for “most readable”:

```julia
struct Roll{R} end
LowRoll = Union{Roll{1},Roll{2},Roll{3}}
HighRoll = Union{Roll{4},Roll{5},Roll{6}}

roll() = Roll{rand(1:6)}()

O6() = D6(roll())
O6(r0) = (r0 == 1 || r0 == 6) ? D6(r0, roll(), r0, "") : (r0, "")

O6(::LowRoll, ::HighRoll, result, mark) = (result, mark) # stop rolling
O6(::HighRoll, ::LowRoll, result, mark) = (result, mark) # stop rolling
O6(::Roll{1}, ::Roll{1}, result, mark) = D6(Roll{1}(), roll(), result-1, "fumble")
O6(::Roll{6}, ::Roll{6}, result, mark) = D6(Roll{6}(), roll(), result+1, "critical")
O6(::LowRoll, r::LowRoll, result, mark) = D6(r, roll(), result-1, mark)
O6(::HighRoll, r::HighRoll, result, mark) = D6(r, roll(), result+1, mark)

```

Obviously that’s painfully slow. Assuming external packages are allowed, this one is much faster while still being quite readable:

```julia
using MLStyle

roll() = rand(1:6)

@active LowRoll(roll) roll ≤ 3
@active HighRoll(roll) roll ≥ 4

O6() = O6(roll())
O6(first_roll) = @match first_roll begin
    1 || 6 => O6(first_roll, roll(), first_roll)
    _ => (first_roll, nothing)
end

O6(r1, r2, result, mark=nothing) = @match (r1, r2) begin
    (1, 1) => O6(1, roll(), result-1, Some("fumble"))
    (6, 6) => O6(6, roll(), result+1, Some("critical"))
    (LowRoll(), LowRoll()) => O6(r2, roll(), result-1, mark)
    (HighRoll(), HighRoll()) => O6(r2, roll(), result+1, mark)
    _ => (result, mark)
end

```

---

<div class="post-metadata">

**Author:** ![Ian\_Slagle](https://avatars.discourse-cdn.com/v4/letter/i/b5a626/32.png) [@Ian\_Slagle](https://discourse.julialang.org/u/Ian_Slagle)\
**Post date:** [May 18, 2020, 8:57pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/11 "2020-05-18T20:57:06Z")

</div>

I think I got the loopless version working. The mark probability can be seen as a function of the number of modifying steps. In this way, it is similar to the probability of getting two heads in a row after n throws of an unfair coin (in this case 1/3 heads, 2/3 tails). This probability I took from ([Two Heads in A Row: With Unequal Probability | ELEC424 Wiki | Fandom](https://elec424.fandom.com/wiki/Two_Heads_in_A_Row:_With_Unequal_Probability)), although there’s not a derivation so I’m not entirely sure it’s correct. Nonetheless, here’s my revised code.

```julia
using Distributions
D(x) = rand(1:x)
P(m) = m <= 1 ? 0 : P(m - 1) + (1 - P(m - 1)) / 3 - (1 - P(m - 2)) * (2 / 9)
function O(x) 
    roll, modifier = D(x), rand(Geometric(0.5))
    if (roll == x || roll == 1) && rand() <= P(modifier)
        println(roll == x ? "$(roll + modifier) critical" : "$(roll - modifier) fumble")
    else
        println(roll == x ? roll + modifier : roll == 1 ? roll - modifier : roll)
    end
end

```

Unfortunately, it’s still not smaller than the looped version, and it certainly makes it less readable, so not sure if there’s any real benefit to this version

---

<div class="post-metadata">

**Author:** ![isene](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/isene/32/14981_2.png) [@isene](https://discourse.julialang.org/u/isene)\
**Post date:** [May 18, 2020, 9:09pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/12 "2020-05-18T21:09:50Z")

</div>

Thanks. This was really instructive for me. I am new to Julia but attracted to it for various reasons. This thread pushed my motivation to go deeper into Julia. BTW (it’s my GitHub repo - the O6 is a way for me to learn new languages).

---

<div class="post-metadata">

**Author:** ![kernelmethod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kernelmethod/32/35480_2.png) [@kernelmethod](https://discourse.julialang.org/u/kernelmethod)\
**Post date:** [May 18, 2020, 9:12pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/13 "2020-05-18T21:12:22Z")

</div>

Oh, also, just for kicks – here’s one which is (i) more compact than the Forth example, (ii) arguably much more readable, and (iii) quite fast (BenchmarkTools.jl gives it a median runtime of ~21ns):

```julia
using MLStyle

O6() = O6(rand(1:6))                                                               
O6(r0) = (r0 == 1 || r0 == 6) ? O6(r0, rand(1:6), r0) : (r0, nothing)
O6(r1, r2, result, mark=nothing) = @match (r1, r2) begin                         
    if r1 ≤ 3 && r2 ≤ 3 end => O6(r2, rand(1:6), result-1, r1 == r2 == 1 ? Some("fumble") : mark)
    if r1 ≥ 4 && r2 ≥ 4 end => O6(r2, rand(1:6), result+1, r1 == r2 == 6 ? Some("critical") : mark)
    _ => (result, mark)
end

```

---

<div class="post-metadata">

**Author:** ![Ian\_Slagle](https://avatars.discourse-cdn.com/v4/letter/i/b5a626/32.png) [@Ian\_Slagle](https://discourse.julialang.org/u/Ian_Slagle)\
**Post date:** [May 18, 2020, 9:25pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/14 "2020-05-18T21:25:36Z")

</div>

My entry for most compact code. I combined everything into one giant ternary statement, and since that felt like cheating, I split the line for (marginally) easier to read code

```julia
using Distributions
P(m) = m <= 1 ? 0 : P(m - 1) + (1 - P(m - 1)) / 3 - (1 - P(m - 2)) * (2 / 9)
function O(x) 
    r, m = rand(1:x), rand(Geometric(0.5))
    println(r == x ? (rand() <= P(m) ? "$(r + m) critical" : r + m) : 
        (r == 1 ? (rand() <= P(m) ? "$(r - m) fumble" : r - m) : r))
end

```

---

<div class="post-metadata">

**Author:** ![Ian\_Slagle](https://avatars.discourse-cdn.com/v4/letter/i/b5a626/32.png) [@Ian\_Slagle](https://discourse.julialang.org/u/Ian_Slagle)\
**Post date:** [May 19, 2020, 3:56am UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/15 "2020-05-19T03:56:41Z")

</div>

And another version of the same length, but not using any packages

```julia
rand_geometric(x = 0) = rand(Bool) ? rand_geometric(x + 1) : x
P(m) = m <= 1 ? 0 : P(m - 1) + (1 - P(m - 1)) / 3 - (1 - P(m - 2)) * (2 / 9)
function O(x) 
    r, m = rand(1:x), rand_geometric()
    println(r == x ? (rand() <= P(m) ? "$(r + m) critical" : r + m) : 
        (r == 1 ? (rand() <= P(m) ? "$(r - m) fumble" : r - m) : r))
end

```

---

<div class="post-metadata">

**Author:** ![tkluck](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkluck/32/15769_2.png) [@tkluck](https://discourse.julialang.org/u/tkluck)\
**Post date:** [May 19, 2020, 6:42am UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/16 "2020-05-19T06:42:02Z")

</div>

> [@kernelmethod](#):
>
> BenchmarkTools.jl gives it a median runtime of ~21ns

Won’t the _median_ runtime be counting only the cases where the first throw is `2-5` (because this accounts for more than 50% of the cases and they are the fastest cases)? If so, it’s not a very meaningful metric to look at because it excludes almost all of the code.

---

<div class="post-metadata">

**Author:** ![isene](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/isene/32/14981_2.png) [@isene](https://discourse.julialang.org/u/isene)\
**Post date:** [May 19, 2020, 10:19am UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/17 "2020-05-19T10:19:02Z")

</div>

On a general note, one can treat all rolls beyond 1 and 6 in the same loop, with a roll of 4-6 adding 1 to an accumulating “open” score and only afterwards checking if that accumulated open score is to be treated as an addition or subtraction to the original dice roll (if the original dice roll was a 1, subtract the accumulated open score and add if the original roll was a 6). The same goes for Criticals and Fumbles, two consecutive 6s is a Critical if the original roll was a 6 and a Fumble if the original roll was a 1 (but remember to then treat the original roll of 1 as a “6” for this purpose). This is how I wrote the FOCAL program (for the HP-41 calculator).

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [May 19, 2020, 12:07pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/18 "2020-05-19T12:07:49Z")

</div>

> [@isene](#):
>
> On a general note, one can treat all rolls beyond 1 and 6 in the same loop

Yes, several of the implementations above do this, e.g. [this one](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/9).

---

<div class="post-metadata">

**Author:** ![tamasgal](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamasgal/32/27946_2.png) [@tamasgal](https://discourse.julialang.org/u/tamasgal)\
**Post date:** [May 19, 2020, 12:20pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/19 "2020-05-19T12:20:33Z")

</div>

I like this, this almost reads like Haskell 😉

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [May 19, 2020, 12:33pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/20 "2020-05-19T12:33:43Z")

</div>

> [@stevengj](#):
>
> With a little statistics knowledge, you could eliminate the loop entirely and write an expression (probably based on `randexp` ) to directly generate an integer equivalent to the number of consecutive `true` values from a sequence of `rand(Bool)` calls.

Conditional on the first draw being `1` or `6`, the subsequent sequence length has [geometric distribution](https://en.wikipedia.org/wiki/Geometric_distribution) with p = 1/2, but no simple closed form solution occurs to me for the probability of “critical” or “fumble” (eg conditional on the sequence length).

A standard trick would be formulating it as an [absorbing Markov chain](https://en.wikipedia.org/wiki/Absorbing_Markov_chain) with the state being the current roll and whether critical/fumble has happened.

---

<div class="post-metadata">

**Author:** ![isene](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/isene/32/14981_2.png) [@isene](https://discourse.julialang.org/u/isene)\
**Post date:** [May 19, 2020, 1:00pm UTC](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702/21 "2020-05-19T13:00:08Z")

</div>

I know - just wanted to make it into a general note.

[Next page](https://discourse.julialang.org/t/opportunity-to-write-some-idiomatic-julia-code/39702.md?page=2)
