Случайная сетка элементов с использованием Haskell

Имея абсолютно нулевой опыт работы с Haskell, мне нужно придумать код, эквивалентный этому Python:

from random import choice, sample

def random_subset():
    return tuple(sample(('N', 'S', 'W', 'E'), choice((1, 2, 3, 4))))

def form_grid(n):
    return [[random_subset() for _ in range(n)] for _ in range(n)]

form_grid(10)

который производит что-то вроде этого:

N     ESWN  SNEW  NSE   EWSN  E     ENSW  N     NSWE  WES   
NE    WNS   SWEN  EWN   ENWS  WEN   WS    W     ENSW  NW    
WENS  NWE   SNEW  ES    E     S     ES    SENW  EW    WEN   
NSE   NE    WNE   NEWS  SNE   W     SWNE  NSWE  SNEW  EN    
S     SNW   WNES  S     WESN  E     ES    N     ESN   ES    
SWEN  S     WSNE  NEWS  WESN  E     S     SE    E     N     
NEW   S     NEW   WS    W     EN    N     NWS   E     WENS  
WN    NWE   S     SEW   NESW  EWSN  WENS  ES    NWS   WN    
W     NWE   N     N     ES    E     E     WN    SWNE  NES   
WENS  NWE   NW    WESN  SW    NES   ENWS  SE    N     SWNE 

Я, ради бога, не могу понять концепцию ввода-вывода Haskell (в частности, случайность). Лучшее, что я мог придумать, это:

import Data.Random hiding (shuffle, sample)
import Data.Random.Source.Std
import Data.Random.Extras

randSubset :: IO [Char]
randSubset = do
    len <- runRVar (choice [1..4]) StdRandom :: IO Int
    subset <- runRVar (sample len ['N', 'S', 'W', 'E']) StdRandom :: IO [Char]
    return subset

formGrid :: Int -> [[IO [Char]]]
formGrid n = [[subset | _ <- [0..(n - 1)], subset <- randSubset] | _ <- [0..(n - 1)]]

который до сих пор этого не сделал:

error:
    * Couldn't match expected type `[IO [Char]]'
                  with actual type `IO [Char]'
    * In the expression: randSubset
      In a stmt of a list comprehension: subset <- randSubset
      In the expression:
        [subset | _ <- [0 .. (n - 1)], subset <- randSubset]
   |
12 | formGrid n = [[subset | _ <- [0..(n - 1)], subset <- randSubset] | _ <- [0..(n - 1)]]
   |                                                      ^^^^^^^^^^

Быстрое гугление не очень помогло - я, вероятно, не использовал самые точные ключевые слова для этой проблемы, с которой я столкнулся. Делать случайные изменения и надеяться на лучшее становится довольно неприятно, но у меня действительно нет ни времени, ни энергии, чтобы погрузиться в Haskell должным образом (хотя это позор), поэтому сейчас я бы хотел, чтобы кто-нибудь просто указал мне на то, что неправильно с этим кодом.

randSubset создает IO [Char], поэтому вы не можете перебирать это.
Willem Van Onsem 22.05.2019 13:03

Вы научились использовать монады и do-нотацию? Вы найдете все IO довольно сложными, пока не поймете это. Я бы рекомендовал глава LYAH о монадах, если не весь LYAH, чтобы понять это.

AJF 22.05.2019 13:08

@Виллем Ван Онсем, ты прав! На самом деле, я даже не хочу повторять это. Я просто хочу взять целые подмножества (каждый раз новые) и поместить их в ячейки n*n раз. Как я могу это сделать?

Enirsa 22.05.2019 13:08
3 метода стилизации элементов HTML
3 метода стилизации элементов HTML
Когда дело доходит до применения какого-либо стиля к нашему HTML, существует три подхода: встроенный, внутренний и внешний. Предпочтительным обычно...
Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
В чем разница между Promise и Observable?
В чем разница между Promise и Observable?
Разберитесь в этом вопросе, и вы значительно повысите уровень своей компетенции.
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Клиент для URL-адресов, cURL, позволяет взаимодействовать с множеством различных серверов по множеству различных протоколов с синтаксисом URL.
2
3
164
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Как уже сказано в ошибке, у вашего понимания списка есть генератор:

formGrid n = [[subset | _ <- [0..(n - 1)], subset <- randSubset] | _ <- [0..(n - 1)]]

Это означает, что он ожидает, что randSubset будет списком чего-то, но это не список чего-то, а IO списка чего-то. Таким образом, вы не можете использовать это.

Тип вашей функции также немного проблематичен, вы используете [[IO [Char]]], поэтому матрица IO [Char]s.

Вероятно, вы ищете replicateM :: Monad m => Int -> m a -> m [a], поэтому ваша программа выглядит так:

import Control.Monad(replicateM)

formGrid :: Int -> IO [[[Char]]]
formGrid n = replicateM n (replicateM n randSubset)

Например:

Main> formGrid 3
[["WSNE","WNS","S"],["WN","SN","WEN"],["SEWN","ESN","NESW"]]

Чтобы дополнить ответ Виллема, я добавлю, что ваш randSubset выглядит довольно сложно. Вот более простой вариант

randSubset :: IO String
randSubset = do
  n <- sample (Uniform 1 4)        -- choose how many elements
  sample (shuffleNofM n 4 "NSWE")  -- take that many elements

(Кстати, это sample из Data.Random)

Вы должны убедиться, что это действительно предполагаемое распределение подмножеств. Обратите внимание, что это неравномерное распределение: N более вероятно, чем NS (или даже оба NS и SN вместе взятые). Также обратите внимание, что может происходить каждая перестановка одного и того же подмножества, поэтому на самом деле мы не выбираем «подмножества». Я не знаю, какой дистрибутив используется вашим кодом Python — в конце концов, он может быть таким же.

Если вы работаете внутри IO, я думаю, что проще использовать sample (someDistribution), чем работать на более низком уровне RVars.

После этого вы можете использовать replicateM для создания сетки, как показал Виллем.

Вы пытались сразу прыгнуть слишком далеко. Начни с малого,

formRow :: Int -> IO [[Char]]
formRow 0 = return [] 
formRow n = do { 
     subset  <- randSubset ;     -- subset :: [Char]   <-  randSubset :: IO [Char]
     subsets <- formRow (n-1) ;  --           ~~~~~~                        ~~~~~~
     return (subset : subsets)   -- IO [[Char]]
     }

... просто используя то, что у вас уже есть, здесь. Затем проделайте то же самое со строками,

formGrid :: Int -> IO [[[Char]]]
formGrid 0 = return [] 
formGrid n = do { 
     row  <- formRow n ;        -- row :: [[Char]]   <-  formRow n :: IO [[Char]]
     rows <- formGrid (n-1) ;   --        ~~~~~~~~                       ~~~~~~~~
     return (row : rows)        -- IO [[[Char]]]
     }

и вы сделали. Не бойтесь обозначения do, это ваш друг. Его легко запрограммировать, так как это и есть его цель.

Когда вы закодируете свой следующий монадический комбинатор, проверьте его тип в Гугле и посмотрите, есть ли он уже в библиотеке.

Действительно, абстрагируясь, мы видим, что переосуществили

replicateM :: Monad m => Int -> m a -> m [a]

formRow n = replicateM n randSubset 
formGrid n = replicateM n (formRow n)
           = replicateM n (replicateM n randSubset)

(так же, как говорит ответ Виллема, но теперь мы знаем как и Зачем).

Другие вопросы по теме