如何使F#模块可配置(或产生类似效果,如对象属性)

时间:2016-07-05 21:56:19

标签: f# functor

我们说我有这些代码:

namespace global
module NumeracyProblemsInTen=
    module private Random=
        let private a=lazy System.Random()
        let getRandom()=a.Force()
        let next()=getRandom().Next()
        let lessThan exclusiveMax=getRandom().Next exclusiveMax
        let pick seq=
            assert(not<|Seq.isEmpty seq)
            lessThan<|Seq.length seq|>Seq.item<|seq
    module UsedNumbers=
        let min,max=1,9 // *want to make these data variable*
        let numbers={min..max}
        let atLeast a=numbers|>Seq.skipWhile((>)a)
        let atMost a=numbers|>Seq.takeWhile((>=)a)
        module Random=
            let private pick=Random.pick
            let pickNumber()=pick numbers
            let atMost=atMost>>pick
    open UsedNumbers
    module AdditionInTen=
        module Addends=
            let max=max-min
            let numbers={min..max}
            let pick()=Random.pick numbers
        open Addends
        let quiz()=
            let addend=pick()
            addend,Random.atMost<|min+max-addend
        let calc(addend,another)=addend+another
    module MultiplyInTen=
        let quiz()=
            let multipiler=Random.pickNumber()
            multipiler,Random.pick{min..max/multipiler}
        let calc(multipiler,another)=multipiler*another
    module SubtractionInTen=
        let minSubtrahend,minResult=min,min
        let minMinuend=minSubtrahend+minResult
        let minuends=atLeast minMinuend
        let quiz()=
            let minuend=Random.pick minuends
            minuend,Random.pick{minSubtrahend..minuend-minResult}
        let calc(minuend,subtrahend)=minuend-subtrahend
    module DeviditionInTen=
        let devisible devidend deviser=devidend%deviser=0
        let findDevisers devidend=numbers|>Seq.filter(devisible devidend)
        let findDeviditions devidend=findDevisers devidend|>Seq.map(fun deviser->devidend,deviser)
        let problems=Seq.collect findDeviditions numbers
        let quiz()=Random.pick problems
        let calc(devidend,deviser)=devidend/deviser
    type Problem=Addition of int*int|Subtraction of int*int|Multiply of int*int|Devidition of int*int
    let quiz()=
        let quizers=[AdditionInTen.quiz>>Addition;SubtractionInTen.quiz>>Subtraction;
            MultiplyInTen.quiz>>Multiply;DeviditionInTen.quiz>>Devidition]
        quizers|>Random.pick<|()
    let calc problem=
        match problem with
            |Addition(addend,another)->AdditionInTen.calc(addend,another)
            |Subtraction(minuend,subtrahend)->SubtractionInTen.calc(minuend,subtrahend)
            |Multiply(multipiler,another)->MultiplyInTen.calc(multipiler,another)
            |Devidition(devidend,deviser)->DeviditionInTen.calc(devidend,deviser)
module NumeracyProblemsUnderOneHundred=
    module UsedNumbers=
        let min,max=1,99
        // ...
    // ...
    // ...
    // OMG! Do I must copy all the previous code here?

如果我使用oo / types,我可以简单地将Max定义为属性,有没有一种很好的方法来解析没有对象/类型的相同场景但只有模块/不可变绑定方式?还应该考虑一些更复杂的场景,更多可配置的数据,以不同的方式使用更多。

1 个答案:

答案 0 :(得分:1)

因此,在我看来,您的代码旨在生成随机数学运算,然后您可以计算结果。我发现这段代码很难破译,看起来你正试图使用​​modules类似面向对象的classes,其中包含内部状态,这并不是一个考虑它们的好方法。 / p>

通过考虑较小的,可组合的代码单元,您可以实现更精细的代码重用。

以下是我对此问题的尝试:

type Range = {Min : int; Max : int}

type Problem= 
    |Addition of int*int
    |Subtraction of int*int
    |Multiplication of int*int
    |Division of int*int

module NumeracyProblems =
    let private rnd = System.Random()

    let randomInRange range = rnd.Next(range.Min, range.Max+1)

    let isInRange range x = x >= range.Min && x <= range.Max

    let randomOpGen() = 
        match randomInRange {Min = 0; Max = 3} with
        |0 -> Addition
        |1 -> Subtraction 
        |2 -> Multiplication
        |3 -> Division

    let calc = function
        |Addition (v1, v2) -> Some(v1 + v2)
        |Subtraction (v1, v2) -> Some(v1 - v2)
        |Multiplication (v1, v2) -> Some(v1 * v2)
        |Division (v1, v2) -> 
            match v1 % v2 = 0 with 
            |true -> Some(v1 / v2)
            |false -> None

    let quiz range =
        let op = randomOpCtor()
        let optionInRange x =
            match isInRange range x with
            |true -> Some x
            |false -> None
        Seq.initInfinite (fun _ -> randomInRange range, randomInRange range)
        |> Seq.map (op)
        |> Seq.find (Option.isSome << Option.bind (optionInRange) << calc)

我创建了一个Range记录来包含我将要使用的范围数据。

我的randomInRange函数会在指定范围内生成一个随机数。

我的isInRange函数确定给定值是否在提供的范围内。

我的randomOpGen函数生成0-3范围内的数字,然后在随机值为1时生成ProblemAddition的随机类型构造函数,{{1}当2等时

(你可能想知道为什么我用Subtraction参数定义了这个函数,而不仅仅是接受元组,答案是这样我可以让它在以后生成具有相同可能性的运算符。)

我的unit函数通过执行适当的操作来解析算术。我修改了这个函数的结果,以便在余数为0的情况下返回calc来处理整数除法,否则返回Some result。所有其他计算始终返回None

Some result是神奇发生的地方。

首先我生成一个随机运算符,这对于序列中的每个元素都是相同的 - 因此我前面提到的quiz的重要性。

我从提供的范围生成一个无限的整数元组序列,并使用()在每个元组上生成一个操作(我之前创建的那个)。

然后我使用map来查找第一次出现的结果,该结果都在我指定的范围内且具有有效的结果值。

现在让我们试试这段代码:

Seq.find

现在让我们改变范围

let x = NumeracyProblems.quiz {Min = 1; Max = 9}
x
NumeracyProblems.calc x;;
val x : Problem = Addition (2,7) 
val it : int option = Some 9

如您所见,此版本的代码与整数范围完全无关。