2016-10-23 7 views
0

У меня есть небольшая программа Haskell, поведение которой должно немного меняться в зависимости от количества предоставленных аргументов. Я не могу решить, как назначить три переменные аккуратным способом, основанным на количестве предоставленных аргументов.Haskell - Назначить переменные с `case` в` do` block

Я хочу использовать что-то вроде выражения case или эквивалента switch на других языках программирования, но не может решить, как это сделать.

Вот мой текущий (неисправный) код, который опирается на Dice.Base модуль для генерации случайных чисел:

{-# LANGUAGE ScopedTypeVariables #-} 
import Dice.Base 
import System.Environment 
import Data.List 

main :: IO() 

--Expected argument formats: 
--(number of dice, number of faces on each die, number to add to total) 
--(number of dice, number of faces on each die) 
--(number of faces on a single die) 
main = do 
     args <- getArgs 

     --Set the number of dice, number of faces on each die, 
      --and number to add to sum of rolls. 
     case (length args) of 
      3 -> let (numDice ::NaturalP) = toNaturalP $ read $ args!!0; 
       let (numFaces::NaturalP) = toNaturalP $ read $ args!!1; 
       let (offset ::Integer) = read $ args!!2; 
      2 -> let (numDice ::NaturalP) = toNaturalP $ read $ args!!0; 
       let (numFaces::NaturalP) = toNaturalP $ read $ args!!1; 
       let (offset ::Integer) = 0; 
      1 -> let (numDice ::NaturalP) = 1; 
       let (numFaces::NaturalP) = toNaturalP $ read $ args!!0; 
       let (offset ::Integer) = 0; 

     -- 'dc' is a structure capturing the Dice Configuration. 
     let dc = NumSidesPlus numDice numFaces offset 
     outcomeInteger <- roll dc 

     putStrLn("Dice configuration:  " ++ (show dc)) 
     putStrLn("Outcome:    " ++ (show outcomeInteger)) 

Это не работает, конечно, потому что case производит выражение; он не предназначен для работы в блоке do.

Я знаю, что можно сделать let (a,b,c) = case ... и назначить три значения в качестве кортежа, но я считаю это обходным путем, потому что это действительно не помогает мне для более сложных проектов. Каковы методы управления потоком в блоке do? Есть ли что-то аналогичное switch как на императивном языке?

Как выполнить эту зависимую переменную?

+1

Возможно, вы могли бы привести небольшой пример сложной логики, для которой кортежей не хватит. – danidiaz

+0

@ danidiaz - Я изо всех сил стараюсь придумать хороший пример. Но предположим, что у меня есть переменные «10», которые я хочу назначить, хотя можно выполнить назначение с помощью кортежей, я чувствую, что это как-то плохая практика и беспорядок, чтобы она распространилась на 10 строк. Мне нравится подход к назначению переменных один за другим, потому что мне легче читать и понимать. – Myridium

+0

В этот момент, возможно, лучшим подходом было бы определить запись с именованными полями для хранения проанализированных опций, а также использовать некоторые из существующих библиотек анализа аргументов, таких как «optparse-generic» или (более сложные и полные -featured) "optparse-applyative". – danidiaz

ответ

2

Вы должны позволить связать переменные для выражения и имеют выражение определяет значение, не предполагают, что вы можете сделать некоторое pathalogical объявления переменного:

let (numDice,numFaces,offset) = 
    case (length args) of 
     3 -> (toNaturalP $ read $ args!!0 
      ,toNaturalP $ read $ args!!1 
      ,read $ args!!2) 
     2 -> (toNaturalP $ read $ args!!0 
      ,toNaturalP $ read $ args!!1 
      ,0) 
     1 -> (1 
      ,toNaturalP $ read $ args!!0 
      ,0) 
+0

Я хотел избегать группировки их в кортеж, потому что в общем случае условное поведение может быть намного сложнее, чем просто несколько переменных назначений. Есть ли альтернативный способ? Нет ли опции для управления потоком в блоке 'do'? – Myridium

+0

Кроме того, это создает ошибку синтаксического анализа: «Возможные неправильные отступы или несогласованные скобки» в строке с оператором 'case'. Однако я не вижу проблемы с отступом. – Myridium

+0

То, что я предоставил, - это смещение - вам нужно встроить его в блок 'do' при правильном отступе. WRT «поток управления», что я получаю, заключается в том, что вы хотите изменить переменные или какие-то переменные, которые избегают их синтаксической области. Вы можете создавать изменчивые ссылки, но это не так полезно, как вы могли бы надеяться, что переменные не смогут избежать их возможностей. Вы можете объявлять переменные «один за другим» просто через 'let numDice = expr1; numFaces = expr2; ... ', но, похоже, эти exprs будут делиться достаточным количеством логики в вашем случае. –

4

Вместо переключения длины списка, а затем индексация, я предпочел бы шаблон матча непосредственно в списке, а также с помощью ViewPatterns:

main :: IO() 
main = do 
    args <- getArgs 
    let (arg1,arg2) = case args of 
         [] -> (1::Int,"default") 
         [read -> arg1, read -> arg2] -> (arg1,arg2) 
    print (arg1,arg2)  

Если кортеж не достаточно, потому что логика извлечения сложна, я бы определить пользовательский тип данных, проведение возможного Alterna ставители.

Вы также можете использовать guards и расширение PatternGuards, чтобы наложить сложные условия в каждом матче.

+0

Мне это нравится; полезно знать о «ViewPatterns». Я знаю, как использовать выражения 'PatternGuards' и' case' и 'if' для управления потоком выражения, но есть ли какие-либо такие опции управления потоком внутри блока' do'? – Myridium

+0

@Myridium Вы можете использовать 'case' и' if' в do-blocks, без проблем. Обратите внимание, что в вашем примере 'let' находятся внутри ветвей 'case'. В моем ответе let связывает результат всего соответствия шаблону, и значения доступны в остальной части блока do. Theres тонкость: внутри do-blocks «давайте не будем». – danidiaz